From cbdc71fee37ce26c0a05cabc55cb03b46c29b216 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 16:10:10 +0000 Subject: [PATCH 1/9] fix(client): preserve hardcoded query params when merging with user params --- src/imagekitio/_base_client.py | 4 +++ tests/test_client.py | 48 ++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/imagekitio/_base_client.py b/src/imagekitio/_base_client.py index 121c3d4..8be3f93 100644 --- a/src/imagekitio/_base_client.py +++ b/src/imagekitio/_base_client.py @@ -540,6 +540,10 @@ def _build_request( files = cast(HttpxRequestFiles, ForceMultipartDict()) prepared_url = self._prepare_url(options.url) + # preserve hard-coded query params from the url + if params and prepared_url.query: + params = {**dict(prepared_url.params.items()), **params} + prepared_url = prepared_url.copy_with(raw_path=prepared_url.raw_path.split(b"?", 1)[0]) if "_" in prepared_url.host: # work around https://github.com/encode/httpx/discussions/2880 kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")} diff --git a/tests/test_client.py b/tests/test_client.py index 00e1367..3abbbf3 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -475,6 +475,30 @@ def test_default_query_option(self) -> None: client.close() + def test_hardcoded_query_params_in_url(self, client: ImageKit) -> None: + request = client._build_request(FinalRequestOptions(method="get", url="/foo?beta=true")) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true"} + + request = client._build_request( + FinalRequestOptions( + method="get", + url="/foo?beta=true", + params={"limit": "10", "page": "abc"}, + ) + ) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true", "limit": "10", "page": "abc"} + + request = client._build_request( + FinalRequestOptions( + method="get", + url="/files/a%2Fb?beta=true", + params={"limit": "10"}, + ) + ) + assert request.url.raw_path == b"/files/a%2Fb?beta=true&limit=10" + def test_request_extra_json(self, client: ImageKit) -> None: request = client._build_request( FinalRequestOptions( @@ -1455,6 +1479,30 @@ async def test_default_query_option(self) -> None: await client.close() + async def test_hardcoded_query_params_in_url(self, async_client: AsyncImageKit) -> None: + request = async_client._build_request(FinalRequestOptions(method="get", url="/foo?beta=true")) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true"} + + request = async_client._build_request( + FinalRequestOptions( + method="get", + url="/foo?beta=true", + params={"limit": "10", "page": "abc"}, + ) + ) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true", "limit": "10", "page": "abc"} + + request = async_client._build_request( + FinalRequestOptions( + method="get", + url="/files/a%2Fb?beta=true", + params={"limit": "10"}, + ) + ) + assert request.url.raw_path == b"/files/a%2Fb?beta=true&limit=10" + def test_request_extra_json(self, client: ImageKit) -> None: request = client._build_request( FinalRequestOptions( From 8803680ae4bb3ea801d71520cc1354b7a1558bc6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 06:41:59 +0000 Subject: [PATCH 2/9] feat(api): dam related webhook events --- .stats.yml | 6 +- README.md | 19 +- api.md | 5 + src/imagekitio/_client.py | 38 ---- src/imagekitio/types/__init__.py | 5 + src/imagekitio/types/dam_file_create_event.py | 22 ++ src/imagekitio/types/dam_file_delete_event.py | 28 +++ src/imagekitio/types/dam_file_update_event.py | 22 ++ .../types/dam_file_version_create_event.py | 20 ++ .../types/dam_file_version_delete_event.py | 31 +++ .../types/shared/extension_config.py | 12 +- src/imagekitio/types/shared/extensions.py | 12 +- .../types/shared/overlay_position.py | 34 +-- .../types/shared_params/extension_config.py | 12 +- .../types/shared_params/extensions.py | 12 +- .../types/shared_params/overlay_position.py | 37 +--- .../types/unsafe_unwrap_webhook_event.py | 10 + src/imagekitio/types/unwrap_webhook_event.py | 10 + tests/api_resources/test_dummy.py | 78 ------- tests/conftest.py | 11 +- tests/test_client.py | 203 +++--------------- 21 files changed, 217 insertions(+), 410 deletions(-) create mode 100644 src/imagekitio/types/dam_file_create_event.py create mode 100644 src/imagekitio/types/dam_file_delete_event.py create mode 100644 src/imagekitio/types/dam_file_update_event.py create mode 100644 src/imagekitio/types/dam_file_version_create_event.py create mode 100644 src/imagekitio/types/dam_file_version_delete_event.py diff --git a/.stats.yml b/.stats.yml index 0e8db84..e53249b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 48 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-63aff1629530786015da3c86131afa8a9b60545d488884b77641f1d4b89c6e9d.yml -openapi_spec_hash: 586d357bd7e5217d240a99e0d83c6d1f -config_hash: 47cb702ee2cb52c58d803ae39ade9b44 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-1422f7513f230162270b197061e5768c2e0c803b94b8cd03a5e72544ac75a27f.yml +openapi_spec_hash: 41175e752e6f6ce900b36aecba687fa7 +config_hash: 17e408231b0b01676298010c7405f483 diff --git a/README.md b/README.md index 6ea03a4..2d6dc77 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,6 @@ pip install imagekitio The full API of this library can be found in [api.md](api.md). ```python -import os from imagekitio import ImageKit client = ImageKit( @@ -122,7 +121,6 @@ print(response.file_id) Simply import `AsyncImageKit` instead of `ImageKit` and use `await` with each API call: ```python -import os import asyncio from imagekitio import AsyncImageKit @@ -163,7 +161,6 @@ pip install imagekitio[aiohttp] Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: ```python -import os import asyncio from imagekitio import DefaultAioHttpClient from imagekitio import AsyncImageKit @@ -506,7 +503,9 @@ from pathlib import Path from imagekitio import ImageKit import io -client = ImageKit() +client = ImageKit( + private_key="My Private Key", +) # Method 1: Upload from bytes # Read file into memory first, then upload @@ -580,7 +579,9 @@ All errors inherit from `imagekitio.APIError`. import imagekitio from imagekitio import ImageKit -client = ImageKit() +client = ImageKit( + private_key="My Private Key", +) try: # Read file into memory and upload @@ -628,6 +629,7 @@ from imagekitio import ImageKit # Configure the default for all requests: client = ImageKit( + private_key="My Private Key", # default is 2 max_retries=0, ) @@ -652,12 +654,14 @@ from imagekitio import ImageKit # Configure the default for all requests: client = ImageKit( + private_key="My Private Key", # 20 seconds (default is 1 minute) timeout=20.0, ) # More granular control: client = ImageKit( + private_key="My Private Key", timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0), ) @@ -796,6 +800,7 @@ import httpx from imagekitio import ImageKit, DefaultHttpxClient client = ImageKit( + private_key="My Private Key", # Or use the `IMAGE_KIT_BASE_URL` env var base_url="http://my.test.server.example.com:8083", http_client=DefaultHttpxClient( @@ -818,7 +823,9 @@ By default the library closes underlying HTTP connections whenever the client is ```py from imagekitio import ImageKit -with ImageKit() as client: +with ImageKit( + private_key="My Private Key", +) as client: # make requests here ... diff --git a/api.md b/api.md index 2a17f3a..c3dac75 100644 --- a/api.md +++ b/api.md @@ -265,6 +265,11 @@ Types: ```python from imagekitio.types import ( BaseWebhookEvent, + DamFileCreateEvent, + DamFileDeleteEvent, + DamFileUpdateEvent, + DamFileVersionCreateEvent, + DamFileVersionDeleteEvent, UploadPostTransformErrorEvent, UploadPostTransformSuccessEvent, UploadPreTransformErrorEvent, diff --git a/src/imagekitio/_client.py b/src/imagekitio/_client.py index d0e544d..289c9c7 100644 --- a/src/imagekitio/_client.py +++ b/src/imagekitio/_client.py @@ -3,7 +3,6 @@ from __future__ import annotations import os -import base64 from typing import TYPE_CHECKING, Any, Mapping from typing_extensions import Self, override @@ -13,7 +12,6 @@ from ._qs import Querystring from ._types import ( Omit, - Headers, Timeout, NotGiven, Transport, @@ -218,15 +216,6 @@ def with_streaming_response(self) -> ImageKitWithStreamedResponse: def qs(self) -> Querystring: return Querystring(array_format="comma") - @property - @override - def auth_headers(self) -> dict[str, str]: - if self.password is None: - return {} - credentials = f"{self.private_key}:{self.password}".encode("ascii") - header = f"Basic {base64.b64encode(credentials).decode('ascii')}" - return {"Authorization": header} - @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -236,15 +225,6 @@ def default_headers(self) -> dict[str, str | Omit]: **self._custom_headers, } - @override - def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: - if headers.get("Authorization") or isinstance(custom_headers.get("Authorization"), Omit): - return - - raise TypeError( - '"Could not resolve authentication method. Expected the private_key or password to be set. Or for the `Authorization` headers to be explicitly omitted"' - ) - def copy( self, *, @@ -486,15 +466,6 @@ def with_streaming_response(self) -> AsyncImageKitWithStreamedResponse: def qs(self) -> Querystring: return Querystring(array_format="comma") - @property - @override - def auth_headers(self) -> dict[str, str]: - if self.password is None: - return {} - credentials = f"{self.private_key}:{self.password}".encode("ascii") - header = f"Basic {base64.b64encode(credentials).decode('ascii')}" - return {"Authorization": header} - @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -504,15 +475,6 @@ def default_headers(self) -> dict[str, str | Omit]: **self._custom_headers, } - @override - def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: - if headers.get("Authorization") or isinstance(custom_headers.get("Authorization"), Omit): - return - - raise TypeError( - '"Could not resolve authentication method. Expected the private_key or password to be set. Or for the `Authorization` headers to be explicitly omitted"' - ) - def copy( self, *, diff --git a/src/imagekitio/types/__init__.py b/src/imagekitio/types/__init__.py index 180fe49..0adca51 100644 --- a/src/imagekitio/types/__init__.py +++ b/src/imagekitio/types/__init__.py @@ -53,11 +53,16 @@ from .folder_rename_params import FolderRenameParams as FolderRenameParams from .unwrap_webhook_event import UnwrapWebhookEvent as UnwrapWebhookEvent from .custom_metadata_field import CustomMetadataField as CustomMetadataField +from .dam_file_create_event import DamFileCreateEvent as DamFileCreateEvent +from .dam_file_delete_event import DamFileDeleteEvent as DamFileDeleteEvent +from .dam_file_update_event import DamFileUpdateEvent as DamFileUpdateEvent from .folder_create_response import FolderCreateResponse as FolderCreateResponse from .folder_delete_response import FolderDeleteResponse as FolderDeleteResponse from .folder_rename_response import FolderRenameResponse as FolderRenameResponse from .update_file_request_param import UpdateFileRequestParam as UpdateFileRequestParam from .unsafe_unwrap_webhook_event import UnsafeUnwrapWebhookEvent as UnsafeUnwrapWebhookEvent +from .dam_file_version_create_event import DamFileVersionCreateEvent as DamFileVersionCreateEvent +from .dam_file_version_delete_event import DamFileVersionDeleteEvent as DamFileVersionDeleteEvent from .saved_extension_create_params import SavedExtensionCreateParams as SavedExtensionCreateParams from .saved_extension_list_response import SavedExtensionListResponse as SavedExtensionListResponse from .saved_extension_update_params import SavedExtensionUpdateParams as SavedExtensionUpdateParams diff --git a/src/imagekitio/types/dam_file_create_event.py b/src/imagekitio/types/dam_file_create_event.py new file mode 100644 index 0000000..cc4388a --- /dev/null +++ b/src/imagekitio/types/dam_file_create_event.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .file import File +from .base_webhook_event import BaseWebhookEvent + +__all__ = ["DamFileCreateEvent"] + + +class DamFileCreateEvent(BaseWebhookEvent): + """Triggered when a file is created.""" + + created_at: datetime + """Timestamp of when the event occurred in ISO8601 format.""" + + data: File + """Object containing details of a file or file version.""" + + type: Literal["file.created"] # type: ignore + """Type of the webhook event.""" diff --git a/src/imagekitio/types/dam_file_delete_event.py b/src/imagekitio/types/dam_file_delete_event.py new file mode 100644 index 0000000..ef32762 --- /dev/null +++ b/src/imagekitio/types/dam_file_delete_event.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .base_webhook_event import BaseWebhookEvent + +__all__ = ["DamFileDeleteEvent", "DamFileDeleteEventData"] + + +class DamFileDeleteEventData(BaseModel): + file_id: str = FieldInfo(alias="fileId") + """The unique `fileId` of the deleted file.""" + + +class DamFileDeleteEvent(BaseWebhookEvent): + """Triggered when a file is deleted.""" + + created_at: datetime + """Timestamp of when the event occurred in ISO8601 format.""" + + data: DamFileDeleteEventData + + type: Literal["file.deleted"] # type: ignore + """Type of the webhook event.""" diff --git a/src/imagekitio/types/dam_file_update_event.py b/src/imagekitio/types/dam_file_update_event.py new file mode 100644 index 0000000..77b4d7c --- /dev/null +++ b/src/imagekitio/types/dam_file_update_event.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .file import File +from .base_webhook_event import BaseWebhookEvent + +__all__ = ["DamFileUpdateEvent"] + + +class DamFileUpdateEvent(BaseWebhookEvent): + """Triggered when a file is updated.""" + + created_at: datetime + """Timestamp of when the event occurred in ISO8601 format.""" + + data: File + """Object containing details of a file or file version.""" + + type: Literal["file.updated"] # type: ignore + """Type of the webhook event.""" diff --git a/src/imagekitio/types/dam_file_version_create_event.py b/src/imagekitio/types/dam_file_version_create_event.py new file mode 100644 index 0000000..9199918 --- /dev/null +++ b/src/imagekitio/types/dam_file_version_create_event.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .base_webhook_event import BaseWebhookEvent + +__all__ = ["DamFileVersionCreateEvent"] + + +class DamFileVersionCreateEvent(BaseWebhookEvent): + """Triggered when a file version is created.""" + + created_at: datetime + """Timestamp of when the event occurred in ISO8601 format.""" + + data: object + + type: Literal["file-version.created"] # type: ignore + """Type of the webhook event.""" diff --git a/src/imagekitio/types/dam_file_version_delete_event.py b/src/imagekitio/types/dam_file_version_delete_event.py new file mode 100644 index 0000000..267422a --- /dev/null +++ b/src/imagekitio/types/dam_file_version_delete_event.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .base_webhook_event import BaseWebhookEvent + +__all__ = ["DamFileVersionDeleteEvent", "DamFileVersionDeleteEventData"] + + +class DamFileVersionDeleteEventData(BaseModel): + file_id: str = FieldInfo(alias="fileId") + """The unique `fileId` of the deleted file.""" + + version_id: str = FieldInfo(alias="versionId") + """The unique `versionId` of the deleted file version.""" + + +class DamFileVersionDeleteEvent(BaseWebhookEvent): + """Triggered when a file version is deleted.""" + + created_at: datetime + """Timestamp of when the event occurred in ISO8601 format.""" + + data: DamFileVersionDeleteEventData + + type: Literal["file-version.deleted"] # type: ignore + """Type of the webhook event.""" diff --git a/src/imagekitio/types/shared/extension_config.py b/src/imagekitio/types/shared/extension_config.py index 671e0ac..5cc15e9 100644 --- a/src/imagekitio/types/shared/extension_config.py +++ b/src/imagekitio/types/shared/extension_config.py @@ -99,9 +99,8 @@ class AITasksTaskSelectTags(BaseModel): vocabulary: Optional[List[str]] = None """Array of possible tag values. - The combined length of all strings must not exceed 500 characters, and values - cannot include the `%` character. When providing large vocabularies (more than - 30 items), the AI may not follow the list strictly. + Combined length of all strings must not exceed 500 characters. Cannot contain + the `%` character. """ @@ -125,12 +124,7 @@ class AITasksTaskSelectMetadata(BaseModel): """Minimum number of values to select from the vocabulary.""" vocabulary: Optional[List[Union[str, float, bool]]] = None - """An array of possible values matching the custom metadata field type. - - If not provided for SingleSelect or MultiSelect field types, all values from the - custom metadata field definition will be used. When providing large vocabularies - (above 30 items), the AI may not strictly adhere to the list. - """ + """Array of possible values matching the custom metadata field type.""" class AITasksTaskYesNoOnNoSetMetadata(BaseModel): diff --git a/src/imagekitio/types/shared/extensions.py b/src/imagekitio/types/shared/extensions.py index c680a09..94f132c 100644 --- a/src/imagekitio/types/shared/extensions.py +++ b/src/imagekitio/types/shared/extensions.py @@ -101,9 +101,8 @@ class ExtensionItemAITasksTaskSelectTags(BaseModel): vocabulary: Optional[List[str]] = None """Array of possible tag values. - The combined length of all strings must not exceed 500 characters, and values - cannot include the `%` character. When providing large vocabularies (more than - 30 items), the AI may not follow the list strictly. + Combined length of all strings must not exceed 500 characters. Cannot contain + the `%` character. """ @@ -127,12 +126,7 @@ class ExtensionItemAITasksTaskSelectMetadata(BaseModel): """Minimum number of values to select from the vocabulary.""" vocabulary: Optional[List[Union[str, float, bool]]] = None - """An array of possible values matching the custom metadata field type. - - If not provided for SingleSelect or MultiSelect field types, all values from the - custom metadata field definition will be used. When providing large vocabularies - (above 30 items), the AI may not strictly adhere to the list. - """ + """Array of possible values matching the custom metadata field type.""" class ExtensionItemAITasksTaskYesNoOnNoSetMetadata(BaseModel): diff --git a/src/imagekitio/types/shared/overlay_position.py b/src/imagekitio/types/shared/overlay_position.py index f6cc75c..a6fe5f8 100644 --- a/src/imagekitio/types/shared/overlay_position.py +++ b/src/imagekitio/types/shared/overlay_position.py @@ -3,30 +3,18 @@ from typing import Union, Optional from typing_extensions import Literal -from pydantic import Field as FieldInfo - from ..._models import BaseModel __all__ = ["OverlayPosition"] class OverlayPosition(BaseModel): - anchor_point: Optional[ - Literal["top", "left", "right", "bottom", "top_left", "top_right", "bottom_left", "bottom_right", "center"] - ] = FieldInfo(alias="anchorPoint", default=None) - """ - Sets the anchor point on the base asset from which the overlay offset is - calculated. The default value is `top_left`. Maps to `lap` in the URL. Can only - be used with one or more of `x`, `y`, `xCenter`, or `yCenter`. - """ - focus: Optional[ Literal["center", "top", "left", "bottom", "right", "top_left", "top_right", "bottom_left", "bottom_right"] ] = None """ - Specifies the position of the overlay relative to the parent image or video. If - one or more of `x`, `y`, `xCenter`, or `yCenter` parameters are specified, this - parameter is ignored. Maps to `lfo` in the URL. + Specifies the position of the overlay relative to the parent image or video. + Maps to `lfo` in the URL. """ x: Union[float, str, None] = None @@ -38,15 +26,6 @@ class OverlayPosition(BaseModel): [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). """ - x_center: Union[float, str, None] = FieldInfo(alias="xCenter", default=None) - """ - Specifies the x-coordinate on the base asset where the overlay's center will be - positioned. It also accepts arithmetic expressions such as `bw_mul_0.4` or - `bw_sub_cw`. Maps to `lxc` in the URL. Cannot be used together with `x`, but can - be used with `y`. Learn about - [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). - """ - y: Union[float, str, None] = None """ Specifies the y-coordinate of the top-left corner of the base asset where the @@ -55,12 +34,3 @@ class OverlayPosition(BaseModel): about [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). """ - - y_center: Union[float, str, None] = FieldInfo(alias="yCenter", default=None) - """ - Specifies the y-coordinate on the base asset where the overlay's center will be - positioned. It also accepts arithmetic expressions such as `bh_mul_0.4` or - `bh_sub_ch`. Maps to `lyc` in the URL. Cannot be used together with `y`, but can - be used with `x`. Learn about - [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). - """ diff --git a/src/imagekitio/types/shared_params/extension_config.py b/src/imagekitio/types/shared_params/extension_config.py index 78458ad..453fb73 100644 --- a/src/imagekitio/types/shared_params/extension_config.py +++ b/src/imagekitio/types/shared_params/extension_config.py @@ -99,9 +99,8 @@ class AITasksTaskSelectTags(TypedDict, total=False): vocabulary: SequenceNotStr[str] """Array of possible tag values. - The combined length of all strings must not exceed 500 characters, and values - cannot include the `%` character. When providing large vocabularies (more than - 30 items), the AI may not follow the list strictly. + Combined length of all strings must not exceed 500 characters. Cannot contain + the `%` character. """ @@ -125,12 +124,7 @@ class AITasksTaskSelectMetadata(TypedDict, total=False): """Minimum number of values to select from the vocabulary.""" vocabulary: SequenceNotStr[Union[str, float, bool]] - """An array of possible values matching the custom metadata field type. - - If not provided for SingleSelect or MultiSelect field types, all values from the - custom metadata field definition will be used. When providing large vocabularies - (above 30 items), the AI may not strictly adhere to the list. - """ + """Array of possible values matching the custom metadata field type.""" class AITasksTaskYesNoOnNoSetMetadata(TypedDict, total=False): diff --git a/src/imagekitio/types/shared_params/extensions.py b/src/imagekitio/types/shared_params/extensions.py index 176b80e..f4c8149 100644 --- a/src/imagekitio/types/shared_params/extensions.py +++ b/src/imagekitio/types/shared_params/extensions.py @@ -101,9 +101,8 @@ class ExtensionItemAITasksTaskSelectTags(TypedDict, total=False): vocabulary: SequenceNotStr[str] """Array of possible tag values. - The combined length of all strings must not exceed 500 characters, and values - cannot include the `%` character. When providing large vocabularies (more than - 30 items), the AI may not follow the list strictly. + Combined length of all strings must not exceed 500 characters. Cannot contain + the `%` character. """ @@ -127,12 +126,7 @@ class ExtensionItemAITasksTaskSelectMetadata(TypedDict, total=False): """Minimum number of values to select from the vocabulary.""" vocabulary: SequenceNotStr[Union[str, float, bool]] - """An array of possible values matching the custom metadata field type. - - If not provided for SingleSelect or MultiSelect field types, all values from the - custom metadata field definition will be used. When providing large vocabularies - (above 30 items), the AI may not strictly adhere to the list. - """ + """Array of possible values matching the custom metadata field type.""" class ExtensionItemAITasksTaskYesNoOnNoSetMetadata(TypedDict, total=False): diff --git a/src/imagekitio/types/shared_params/overlay_position.py b/src/imagekitio/types/shared_params/overlay_position.py index 2af7068..f74e3e1 100644 --- a/src/imagekitio/types/shared_params/overlay_position.py +++ b/src/imagekitio/types/shared_params/overlay_position.py @@ -3,29 +3,16 @@ from __future__ import annotations from typing import Union -from typing_extensions import Literal, Annotated, TypedDict - -from ..._utils import PropertyInfo +from typing_extensions import Literal, TypedDict __all__ = ["OverlayPosition"] class OverlayPosition(TypedDict, total=False): - anchor_point: Annotated[ - Literal["top", "left", "right", "bottom", "top_left", "top_right", "bottom_left", "bottom_right", "center"], - PropertyInfo(alias="anchorPoint"), - ] - """ - Sets the anchor point on the base asset from which the overlay offset is - calculated. The default value is `top_left`. Maps to `lap` in the URL. Can only - be used with one or more of `x`, `y`, `xCenter`, or `yCenter`. - """ - focus: Literal["center", "top", "left", "bottom", "right", "top_left", "top_right", "bottom_left", "bottom_right"] """ - Specifies the position of the overlay relative to the parent image or video. If - one or more of `x`, `y`, `xCenter`, or `yCenter` parameters are specified, this - parameter is ignored. Maps to `lfo` in the URL. + Specifies the position of the overlay relative to the parent image or video. + Maps to `lfo` in the URL. """ x: Union[float, str] @@ -37,15 +24,6 @@ class OverlayPosition(TypedDict, total=False): [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). """ - x_center: Annotated[Union[float, str], PropertyInfo(alias="xCenter")] - """ - Specifies the x-coordinate on the base asset where the overlay's center will be - positioned. It also accepts arithmetic expressions such as `bw_mul_0.4` or - `bw_sub_cw`. Maps to `lxc` in the URL. Cannot be used together with `x`, but can - be used with `y`. Learn about - [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). - """ - y: Union[float, str] """ Specifies the y-coordinate of the top-left corner of the base asset where the @@ -54,12 +32,3 @@ class OverlayPosition(TypedDict, total=False): about [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). """ - - y_center: Annotated[Union[float, str], PropertyInfo(alias="yCenter")] - """ - Specifies the y-coordinate on the base asset where the overlay's center will be - positioned. It also accepts arithmetic expressions such as `bh_mul_0.4` or - `bh_sub_ch`. Maps to `lyc` in the URL. Cannot be used together with `y`, but can - be used with `x`. Learn about - [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). - """ diff --git a/src/imagekitio/types/unsafe_unwrap_webhook_event.py b/src/imagekitio/types/unsafe_unwrap_webhook_event.py index 9ed05b3..95ed5cf 100644 --- a/src/imagekitio/types/unsafe_unwrap_webhook_event.py +++ b/src/imagekitio/types/unsafe_unwrap_webhook_event.py @@ -4,6 +4,11 @@ from typing_extensions import Annotated, TypeAlias from .._utils import PropertyInfo +from .dam_file_create_event import DamFileCreateEvent +from .dam_file_delete_event import DamFileDeleteEvent +from .dam_file_update_event import DamFileUpdateEvent +from .dam_file_version_create_event import DamFileVersionCreateEvent +from .dam_file_version_delete_event import DamFileVersionDeleteEvent from .upload_pre_transform_error_event import UploadPreTransformErrorEvent from .video_transformation_error_event import VideoTransformationErrorEvent from .video_transformation_ready_event import VideoTransformationReadyEvent @@ -23,6 +28,11 @@ UploadPreTransformErrorEvent, UploadPostTransformSuccessEvent, UploadPostTransformErrorEvent, + DamFileCreateEvent, + DamFileUpdateEvent, + DamFileDeleteEvent, + DamFileVersionCreateEvent, + DamFileVersionDeleteEvent, ], PropertyInfo(discriminator="type"), ] diff --git a/src/imagekitio/types/unwrap_webhook_event.py b/src/imagekitio/types/unwrap_webhook_event.py index e67355f..5d87d50 100644 --- a/src/imagekitio/types/unwrap_webhook_event.py +++ b/src/imagekitio/types/unwrap_webhook_event.py @@ -4,6 +4,11 @@ from typing_extensions import Annotated, TypeAlias from .._utils import PropertyInfo +from .dam_file_create_event import DamFileCreateEvent +from .dam_file_delete_event import DamFileDeleteEvent +from .dam_file_update_event import DamFileUpdateEvent +from .dam_file_version_create_event import DamFileVersionCreateEvent +from .dam_file_version_delete_event import DamFileVersionDeleteEvent from .upload_pre_transform_error_event import UploadPreTransformErrorEvent from .video_transformation_error_event import VideoTransformationErrorEvent from .video_transformation_ready_event import VideoTransformationReadyEvent @@ -23,6 +28,11 @@ UploadPreTransformErrorEvent, UploadPostTransformSuccessEvent, UploadPostTransformErrorEvent, + DamFileCreateEvent, + DamFileUpdateEvent, + DamFileDeleteEvent, + DamFileVersionCreateEvent, + DamFileVersionDeleteEvent, ], PropertyInfo(discriminator="type"), ] diff --git a/tests/api_resources/test_dummy.py b/tests/api_resources/test_dummy.py index 655d6c5..11b63ba 100644 --- a/tests/api_resources/test_dummy.py +++ b/tests/api_resources/test_dummy.py @@ -29,12 +29,9 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: base_overlay={ "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -166,12 +163,9 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: "overlay": { "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -229,12 +223,9 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: image_overlay={ "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -283,12 +274,9 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: "overlay": { "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -341,12 +329,9 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: overlay={ "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -375,12 +360,9 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: ], }, overlay_position={ - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, overlay_timing={ "duration": 0, @@ -412,12 +394,9 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: solid_color_overlay={ "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -490,12 +469,9 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: "overlay": { "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -550,12 +526,9 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: subtitle_overlay={ "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -589,12 +562,9 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: text_overlay={ "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -675,12 +645,9 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: "overlay": { "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -732,12 +699,9 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: video_overlay={ "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -786,12 +750,9 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: "overlay": { "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -885,12 +846,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) base_overlay={ "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -1022,12 +980,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) "overlay": { "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -1085,12 +1040,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) image_overlay={ "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -1139,12 +1091,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) "overlay": { "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -1197,12 +1146,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) overlay={ "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -1231,12 +1177,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) ], }, overlay_position={ - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, overlay_timing={ "duration": 0, @@ -1268,12 +1211,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) solid_color_overlay={ "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -1346,12 +1286,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) "overlay": { "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -1406,12 +1343,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) subtitle_overlay={ "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -1445,12 +1379,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) text_overlay={ "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -1531,12 +1462,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) "overlay": { "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -1588,12 +1516,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) video_overlay={ "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, @@ -1642,12 +1567,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) "overlay": { "layer_mode": "multiply", "position": { - "anchor_point": "top", "focus": "center", "x": 0, - "x_center": 0, "y": 0, - "y_center": 0, }, "timing": { "duration": 0, diff --git a/tests/conftest.py b/tests/conftest.py index d64313c..fc12af7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -46,7 +46,6 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") private_key = "My Private Key" -password = "My Password" @pytest.fixture(scope="session") @@ -55,9 +54,7 @@ def client(request: FixtureRequest) -> Iterator[ImageKit]: if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - with ImageKit( - base_url=base_url, private_key=private_key, password=password, _strict_response_validation=strict - ) as client: + with ImageKit(base_url=base_url, private_key=private_key, _strict_response_validation=strict) as client: yield client @@ -82,10 +79,6 @@ async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncImageKit]: raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict") async with AsyncImageKit( - base_url=base_url, - private_key=private_key, - password=password, - _strict_response_validation=strict, - http_client=http_client, + base_url=base_url, private_key=private_key, _strict_response_validation=strict, http_client=http_client ) as client: yield client diff --git a/tests/test_client.py b/tests/test_client.py index 3abbbf3..4419d81 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -23,7 +23,7 @@ from imagekitio._types import Omit from imagekitio._utils import asyncify from imagekitio._models import BaseModel, FinalRequestOptions -from imagekitio._exceptions import ImageKitError, APIStatusError, APITimeoutError, APIResponseValidationError +from imagekitio._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError from imagekitio._base_client import ( DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, @@ -40,7 +40,6 @@ T = TypeVar("T") base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") private_key = "My Private Key" -password = "My Password" def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]: @@ -141,10 +140,6 @@ def test_copy(self, client: ImageKit) -> None: assert copied.private_key == "another My Private Key" assert client.private_key == "My Private Key" - copied = client.copy(password="another My Password") - assert copied.password == "another My Password" - assert client.password == "My Password" - def test_copy_default_options(self, client: ImageKit) -> None: # options that have a default are overridden correctly copied = client.copy(max_retries=7) @@ -165,7 +160,6 @@ def test_copy_default_headers(self) -> None: client = ImageKit( base_url=base_url, private_key=private_key, - password=password, _strict_response_validation=True, default_headers={"X-Foo": "bar"}, ) @@ -202,11 +196,7 @@ def test_copy_default_headers(self) -> None: def test_copy_default_query(self) -> None: client = ImageKit( - base_url=base_url, - private_key=private_key, - password=password, - _strict_response_validation=True, - default_query={"foo": "bar"}, + base_url=base_url, private_key=private_key, _strict_response_validation=True, default_query={"foo": "bar"} ) assert _get_params(client)["foo"] == "bar" @@ -332,11 +322,7 @@ def test_request_timeout(self, client: ImageKit) -> None: def test_client_timeout_option(self) -> None: client = ImageKit( - base_url=base_url, - private_key=private_key, - password=password, - _strict_response_validation=True, - timeout=httpx.Timeout(0), + base_url=base_url, private_key=private_key, _strict_response_validation=True, timeout=httpx.Timeout(0) ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -349,11 +335,7 @@ def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used with httpx.Client(timeout=None) as http_client: client = ImageKit( - base_url=base_url, - private_key=private_key, - password=password, - _strict_response_validation=True, - http_client=http_client, + base_url=base_url, private_key=private_key, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -365,11 +347,7 @@ def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default with httpx.Client() as http_client: client = ImageKit( - base_url=base_url, - private_key=private_key, - password=password, - _strict_response_validation=True, - http_client=http_client, + base_url=base_url, private_key=private_key, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -381,11 +359,7 @@ def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = ImageKit( - base_url=base_url, - private_key=private_key, - password=password, - _strict_response_validation=True, - http_client=http_client, + base_url=base_url, private_key=private_key, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -400,7 +374,6 @@ async def test_invalid_http_client(self) -> None: ImageKit( base_url=base_url, private_key=private_key, - password=password, _strict_response_validation=True, http_client=cast(Any, http_client), ) @@ -409,7 +382,6 @@ def test_default_headers_option(self) -> None: test_client = ImageKit( base_url=base_url, private_key=private_key, - password=password, _strict_response_validation=True, default_headers={"X-Foo": "bar"}, ) @@ -420,7 +392,6 @@ def test_default_headers_option(self) -> None: test_client2 = ImageKit( base_url=base_url, private_key=private_key, - password=password, _strict_response_validation=True, default_headers={ "X-Foo": "stainless", @@ -434,28 +405,10 @@ def test_default_headers_option(self) -> None: test_client.close() test_client2.close() - def test_validate_headers(self) -> None: - client = ImageKit( - base_url=base_url, private_key=private_key, password=password, _strict_response_validation=True - ) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - assert "Basic" in request.headers.get("Authorization") - - with pytest.raises(ImageKitError): - with update_env( - **{ - "IMAGEKIT_PRIVATE_KEY": Omit(), - "OPTIONAL_IMAGEKIT_IGNORES_THIS": Omit(), - } - ): - client2 = ImageKit(base_url=base_url, private_key=None, password=None, _strict_response_validation=True) - _ = client2 - def test_default_query_option(self) -> None: client = ImageKit( base_url=base_url, private_key=private_key, - password=password, _strict_response_validation=True, default_query={"query_param": "bar"}, ) @@ -654,7 +607,6 @@ def mock_handler(request: httpx.Request) -> httpx.Response: with ImageKit( base_url=base_url, private_key=private_key, - password=password, _strict_response_validation=True, http_client=httpx.Client(transport=MockTransport(handler=mock_handler)), ) as client: @@ -749,10 +701,7 @@ class Model(BaseModel): def test_base_url_setter(self) -> None: client = ImageKit( - base_url="https://example.com/from_init", - private_key=private_key, - password=password, - _strict_response_validation=True, + base_url="https://example.com/from_init", private_key=private_key, _strict_response_validation=True ) assert client.base_url == "https://example.com/from_init/" @@ -764,22 +713,18 @@ def test_base_url_setter(self) -> None: def test_base_url_env(self) -> None: with update_env(IMAGE_KIT_BASE_URL="http://localhost:5000/from/env"): - client = ImageKit(private_key=private_key, password=password, _strict_response_validation=True) + client = ImageKit(private_key=private_key, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @pytest.mark.parametrize( "client", [ ImageKit( - base_url="http://localhost:5000/custom/path/", - private_key=private_key, - password=password, - _strict_response_validation=True, + base_url="http://localhost:5000/custom/path/", private_key=private_key, _strict_response_validation=True ), ImageKit( base_url="http://localhost:5000/custom/path/", private_key=private_key, - password=password, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -801,15 +746,11 @@ def test_base_url_trailing_slash(self, client: ImageKit) -> None: "client", [ ImageKit( - base_url="http://localhost:5000/custom/path/", - private_key=private_key, - password=password, - _strict_response_validation=True, + base_url="http://localhost:5000/custom/path/", private_key=private_key, _strict_response_validation=True ), ImageKit( base_url="http://localhost:5000/custom/path/", private_key=private_key, - password=password, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -831,15 +772,11 @@ def test_base_url_no_trailing_slash(self, client: ImageKit) -> None: "client", [ ImageKit( - base_url="http://localhost:5000/custom/path/", - private_key=private_key, - password=password, - _strict_response_validation=True, + base_url="http://localhost:5000/custom/path/", private_key=private_key, _strict_response_validation=True ), ImageKit( base_url="http://localhost:5000/custom/path/", private_key=private_key, - password=password, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -858,9 +795,7 @@ def test_absolute_request_url(self, client: ImageKit) -> None: client.close() def test_copied_client_does_not_close_http(self) -> None: - test_client = ImageKit( - base_url=base_url, private_key=private_key, password=password, _strict_response_validation=True - ) + test_client = ImageKit(base_url=base_url, private_key=private_key, _strict_response_validation=True) assert not test_client.is_closed() copied = test_client.copy() @@ -871,9 +806,7 @@ def test_copied_client_does_not_close_http(self) -> None: assert not test_client.is_closed() def test_client_context_manager(self) -> None: - test_client = ImageKit( - base_url=base_url, private_key=private_key, password=password, _strict_response_validation=True - ) + test_client = ImageKit(base_url=base_url, private_key=private_key, _strict_response_validation=True) with test_client as c2: assert c2 is test_client assert not c2.is_closed() @@ -897,7 +830,6 @@ def test_client_max_retries_validation(self) -> None: ImageKit( base_url=base_url, private_key=private_key, - password=password, _strict_response_validation=True, max_retries=cast(Any, None), ) @@ -909,16 +841,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = ImageKit( - base_url=base_url, private_key=private_key, password=password, _strict_response_validation=True - ) + strict_client = ImageKit(base_url=base_url, private_key=private_key, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - non_strict_client = ImageKit( - base_url=base_url, private_key=private_key, password=password, _strict_response_validation=False - ) + non_strict_client = ImageKit(base_url=base_url, private_key=private_key, _strict_response_validation=False) response = non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -1141,10 +1069,6 @@ def test_copy(self, async_client: AsyncImageKit) -> None: assert copied.private_key == "another My Private Key" assert async_client.private_key == "My Private Key" - copied = async_client.copy(password="another My Password") - assert copied.password == "another My Password" - assert async_client.password == "My Password" - def test_copy_default_options(self, async_client: AsyncImageKit) -> None: # options that have a default are overridden correctly copied = async_client.copy(max_retries=7) @@ -1165,7 +1089,6 @@ async def test_copy_default_headers(self) -> None: client = AsyncImageKit( base_url=base_url, private_key=private_key, - password=password, _strict_response_validation=True, default_headers={"X-Foo": "bar"}, ) @@ -1202,11 +1125,7 @@ async def test_copy_default_headers(self) -> None: async def test_copy_default_query(self) -> None: client = AsyncImageKit( - base_url=base_url, - private_key=private_key, - password=password, - _strict_response_validation=True, - default_query={"foo": "bar"}, + base_url=base_url, private_key=private_key, _strict_response_validation=True, default_query={"foo": "bar"} ) assert _get_params(client)["foo"] == "bar" @@ -1334,11 +1253,7 @@ async def test_request_timeout(self, async_client: AsyncImageKit) -> None: async def test_client_timeout_option(self) -> None: client = AsyncImageKit( - base_url=base_url, - private_key=private_key, - password=password, - _strict_response_validation=True, - timeout=httpx.Timeout(0), + base_url=base_url, private_key=private_key, _strict_response_validation=True, timeout=httpx.Timeout(0) ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1351,11 +1266,7 @@ async def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used async with httpx.AsyncClient(timeout=None) as http_client: client = AsyncImageKit( - base_url=base_url, - private_key=private_key, - password=password, - _strict_response_validation=True, - http_client=http_client, + base_url=base_url, private_key=private_key, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1367,11 +1278,7 @@ async def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default async with httpx.AsyncClient() as http_client: client = AsyncImageKit( - base_url=base_url, - private_key=private_key, - password=password, - _strict_response_validation=True, - http_client=http_client, + base_url=base_url, private_key=private_key, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1383,11 +1290,7 @@ async def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = AsyncImageKit( - base_url=base_url, - private_key=private_key, - password=password, - _strict_response_validation=True, - http_client=http_client, + base_url=base_url, private_key=private_key, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1402,7 +1305,6 @@ def test_invalid_http_client(self) -> None: AsyncImageKit( base_url=base_url, private_key=private_key, - password=password, _strict_response_validation=True, http_client=cast(Any, http_client), ) @@ -1411,7 +1313,6 @@ async def test_default_headers_option(self) -> None: test_client = AsyncImageKit( base_url=base_url, private_key=private_key, - password=password, _strict_response_validation=True, default_headers={"X-Foo": "bar"}, ) @@ -1422,7 +1323,6 @@ async def test_default_headers_option(self) -> None: test_client2 = AsyncImageKit( base_url=base_url, private_key=private_key, - password=password, _strict_response_validation=True, default_headers={ "X-Foo": "stainless", @@ -1436,30 +1336,10 @@ async def test_default_headers_option(self) -> None: await test_client.close() await test_client2.close() - def test_validate_headers(self) -> None: - client = AsyncImageKit( - base_url=base_url, private_key=private_key, password=password, _strict_response_validation=True - ) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - assert "Basic" in request.headers.get("Authorization") - - with pytest.raises(ImageKitError): - with update_env( - **{ - "IMAGEKIT_PRIVATE_KEY": Omit(), - "OPTIONAL_IMAGEKIT_IGNORES_THIS": Omit(), - } - ): - client2 = AsyncImageKit( - base_url=base_url, private_key=None, password=None, _strict_response_validation=True - ) - _ = client2 - async def test_default_query_option(self) -> None: client = AsyncImageKit( base_url=base_url, private_key=private_key, - password=password, _strict_response_validation=True, default_query={"query_param": "bar"}, ) @@ -1658,7 +1538,6 @@ async def mock_handler(request: httpx.Request) -> httpx.Response: async with AsyncImageKit( base_url=base_url, private_key=private_key, - password=password, _strict_response_validation=True, http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)), ) as client: @@ -1757,10 +1636,7 @@ class Model(BaseModel): async def test_base_url_setter(self) -> None: client = AsyncImageKit( - base_url="https://example.com/from_init", - private_key=private_key, - password=password, - _strict_response_validation=True, + base_url="https://example.com/from_init", private_key=private_key, _strict_response_validation=True ) assert client.base_url == "https://example.com/from_init/" @@ -1772,22 +1648,18 @@ async def test_base_url_setter(self) -> None: async def test_base_url_env(self) -> None: with update_env(IMAGE_KIT_BASE_URL="http://localhost:5000/from/env"): - client = AsyncImageKit(private_key=private_key, password=password, _strict_response_validation=True) + client = AsyncImageKit(private_key=private_key, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @pytest.mark.parametrize( "client", [ AsyncImageKit( - base_url="http://localhost:5000/custom/path/", - private_key=private_key, - password=password, - _strict_response_validation=True, + base_url="http://localhost:5000/custom/path/", private_key=private_key, _strict_response_validation=True ), AsyncImageKit( base_url="http://localhost:5000/custom/path/", private_key=private_key, - password=password, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1809,15 +1681,11 @@ async def test_base_url_trailing_slash(self, client: AsyncImageKit) -> None: "client", [ AsyncImageKit( - base_url="http://localhost:5000/custom/path/", - private_key=private_key, - password=password, - _strict_response_validation=True, + base_url="http://localhost:5000/custom/path/", private_key=private_key, _strict_response_validation=True ), AsyncImageKit( base_url="http://localhost:5000/custom/path/", private_key=private_key, - password=password, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1839,15 +1707,11 @@ async def test_base_url_no_trailing_slash(self, client: AsyncImageKit) -> None: "client", [ AsyncImageKit( - base_url="http://localhost:5000/custom/path/", - private_key=private_key, - password=password, - _strict_response_validation=True, + base_url="http://localhost:5000/custom/path/", private_key=private_key, _strict_response_validation=True ), AsyncImageKit( base_url="http://localhost:5000/custom/path/", private_key=private_key, - password=password, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1866,9 +1730,7 @@ async def test_absolute_request_url(self, client: AsyncImageKit) -> None: await client.close() async def test_copied_client_does_not_close_http(self) -> None: - test_client = AsyncImageKit( - base_url=base_url, private_key=private_key, password=password, _strict_response_validation=True - ) + test_client = AsyncImageKit(base_url=base_url, private_key=private_key, _strict_response_validation=True) assert not test_client.is_closed() copied = test_client.copy() @@ -1880,9 +1742,7 @@ async def test_copied_client_does_not_close_http(self) -> None: assert not test_client.is_closed() async def test_client_context_manager(self) -> None: - test_client = AsyncImageKit( - base_url=base_url, private_key=private_key, password=password, _strict_response_validation=True - ) + test_client = AsyncImageKit(base_url=base_url, private_key=private_key, _strict_response_validation=True) async with test_client as c2: assert c2 is test_client assert not c2.is_closed() @@ -1906,7 +1766,6 @@ async def test_client_max_retries_validation(self) -> None: AsyncImageKit( base_url=base_url, private_key=private_key, - password=password, _strict_response_validation=True, max_retries=cast(Any, None), ) @@ -1918,16 +1777,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = AsyncImageKit( - base_url=base_url, private_key=private_key, password=password, _strict_response_validation=True - ) + strict_client = AsyncImageKit(base_url=base_url, private_key=private_key, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - non_strict_client = AsyncImageKit( - base_url=base_url, private_key=private_key, password=password, _strict_response_validation=False - ) + non_strict_client = AsyncImageKit(base_url=base_url, private_key=private_key, _strict_response_validation=False) response = await non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] From 1a2417d4336d1b9403eb1bc2b65187209fe833c7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 07:03:28 +0000 Subject: [PATCH 3/9] feat(api): fix spec indentation --- .stats.yml | 4 +- README.md | 19 ++-- src/imagekitio/_client.py | 38 +++++++ tests/conftest.py | 11 ++- tests/test_client.py | 203 ++++++++++++++++++++++++++++++++------ 5 files changed, 229 insertions(+), 46 deletions(-) diff --git a/.stats.yml b/.stats.yml index e53249b..c7e4cc5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 48 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-1422f7513f230162270b197061e5768c2e0c803b94b8cd03a5e72544ac75a27f.yml -openapi_spec_hash: 41175e752e6f6ce900b36aecba687fa7 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-f4cd00365ba96133e0675eae3d5d3c6ac13874789e2ce69a84310ab64a4f87dd.yml +openapi_spec_hash: dce632cfbb5464a98c0f5d8eb9573d68 config_hash: 17e408231b0b01676298010c7405f483 diff --git a/README.md b/README.md index 2d6dc77..6ea03a4 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ pip install imagekitio The full API of this library can be found in [api.md](api.md). ```python +import os from imagekitio import ImageKit client = ImageKit( @@ -121,6 +122,7 @@ print(response.file_id) Simply import `AsyncImageKit` instead of `ImageKit` and use `await` with each API call: ```python +import os import asyncio from imagekitio import AsyncImageKit @@ -161,6 +163,7 @@ pip install imagekitio[aiohttp] Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: ```python +import os import asyncio from imagekitio import DefaultAioHttpClient from imagekitio import AsyncImageKit @@ -503,9 +506,7 @@ from pathlib import Path from imagekitio import ImageKit import io -client = ImageKit( - private_key="My Private Key", -) +client = ImageKit() # Method 1: Upload from bytes # Read file into memory first, then upload @@ -579,9 +580,7 @@ All errors inherit from `imagekitio.APIError`. import imagekitio from imagekitio import ImageKit -client = ImageKit( - private_key="My Private Key", -) +client = ImageKit() try: # Read file into memory and upload @@ -629,7 +628,6 @@ from imagekitio import ImageKit # Configure the default for all requests: client = ImageKit( - private_key="My Private Key", # default is 2 max_retries=0, ) @@ -654,14 +652,12 @@ from imagekitio import ImageKit # Configure the default for all requests: client = ImageKit( - private_key="My Private Key", # 20 seconds (default is 1 minute) timeout=20.0, ) # More granular control: client = ImageKit( - private_key="My Private Key", timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0), ) @@ -800,7 +796,6 @@ import httpx from imagekitio import ImageKit, DefaultHttpxClient client = ImageKit( - private_key="My Private Key", # Or use the `IMAGE_KIT_BASE_URL` env var base_url="http://my.test.server.example.com:8083", http_client=DefaultHttpxClient( @@ -823,9 +818,7 @@ By default the library closes underlying HTTP connections whenever the client is ```py from imagekitio import ImageKit -with ImageKit( - private_key="My Private Key", -) as client: +with ImageKit() as client: # make requests here ... diff --git a/src/imagekitio/_client.py b/src/imagekitio/_client.py index 289c9c7..d0e544d 100644 --- a/src/imagekitio/_client.py +++ b/src/imagekitio/_client.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +import base64 from typing import TYPE_CHECKING, Any, Mapping from typing_extensions import Self, override @@ -12,6 +13,7 @@ from ._qs import Querystring from ._types import ( Omit, + Headers, Timeout, NotGiven, Transport, @@ -216,6 +218,15 @@ def with_streaming_response(self) -> ImageKitWithStreamedResponse: def qs(self) -> Querystring: return Querystring(array_format="comma") + @property + @override + def auth_headers(self) -> dict[str, str]: + if self.password is None: + return {} + credentials = f"{self.private_key}:{self.password}".encode("ascii") + header = f"Basic {base64.b64encode(credentials).decode('ascii')}" + return {"Authorization": header} + @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -225,6 +236,15 @@ def default_headers(self) -> dict[str, str | Omit]: **self._custom_headers, } + @override + def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: + if headers.get("Authorization") or isinstance(custom_headers.get("Authorization"), Omit): + return + + raise TypeError( + '"Could not resolve authentication method. Expected the private_key or password to be set. Or for the `Authorization` headers to be explicitly omitted"' + ) + def copy( self, *, @@ -466,6 +486,15 @@ def with_streaming_response(self) -> AsyncImageKitWithStreamedResponse: def qs(self) -> Querystring: return Querystring(array_format="comma") + @property + @override + def auth_headers(self) -> dict[str, str]: + if self.password is None: + return {} + credentials = f"{self.private_key}:{self.password}".encode("ascii") + header = f"Basic {base64.b64encode(credentials).decode('ascii')}" + return {"Authorization": header} + @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -475,6 +504,15 @@ def default_headers(self) -> dict[str, str | Omit]: **self._custom_headers, } + @override + def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: + if headers.get("Authorization") or isinstance(custom_headers.get("Authorization"), Omit): + return + + raise TypeError( + '"Could not resolve authentication method. Expected the private_key or password to be set. Or for the `Authorization` headers to be explicitly omitted"' + ) + def copy( self, *, diff --git a/tests/conftest.py b/tests/conftest.py index fc12af7..d64313c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -46,6 +46,7 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") private_key = "My Private Key" +password = "My Password" @pytest.fixture(scope="session") @@ -54,7 +55,9 @@ def client(request: FixtureRequest) -> Iterator[ImageKit]: if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - with ImageKit(base_url=base_url, private_key=private_key, _strict_response_validation=strict) as client: + with ImageKit( + base_url=base_url, private_key=private_key, password=password, _strict_response_validation=strict + ) as client: yield client @@ -79,6 +82,10 @@ async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncImageKit]: raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict") async with AsyncImageKit( - base_url=base_url, private_key=private_key, _strict_response_validation=strict, http_client=http_client + base_url=base_url, + private_key=private_key, + password=password, + _strict_response_validation=strict, + http_client=http_client, ) as client: yield client diff --git a/tests/test_client.py b/tests/test_client.py index 4419d81..3abbbf3 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -23,7 +23,7 @@ from imagekitio._types import Omit from imagekitio._utils import asyncify from imagekitio._models import BaseModel, FinalRequestOptions -from imagekitio._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError +from imagekitio._exceptions import ImageKitError, APIStatusError, APITimeoutError, APIResponseValidationError from imagekitio._base_client import ( DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, @@ -40,6 +40,7 @@ T = TypeVar("T") base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") private_key = "My Private Key" +password = "My Password" def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]: @@ -140,6 +141,10 @@ def test_copy(self, client: ImageKit) -> None: assert copied.private_key == "another My Private Key" assert client.private_key == "My Private Key" + copied = client.copy(password="another My Password") + assert copied.password == "another My Password" + assert client.password == "My Password" + def test_copy_default_options(self, client: ImageKit) -> None: # options that have a default are overridden correctly copied = client.copy(max_retries=7) @@ -160,6 +165,7 @@ def test_copy_default_headers(self) -> None: client = ImageKit( base_url=base_url, private_key=private_key, + password=password, _strict_response_validation=True, default_headers={"X-Foo": "bar"}, ) @@ -196,7 +202,11 @@ def test_copy_default_headers(self) -> None: def test_copy_default_query(self) -> None: client = ImageKit( - base_url=base_url, private_key=private_key, _strict_response_validation=True, default_query={"foo": "bar"} + base_url=base_url, + private_key=private_key, + password=password, + _strict_response_validation=True, + default_query={"foo": "bar"}, ) assert _get_params(client)["foo"] == "bar" @@ -322,7 +332,11 @@ def test_request_timeout(self, client: ImageKit) -> None: def test_client_timeout_option(self) -> None: client = ImageKit( - base_url=base_url, private_key=private_key, _strict_response_validation=True, timeout=httpx.Timeout(0) + base_url=base_url, + private_key=private_key, + password=password, + _strict_response_validation=True, + timeout=httpx.Timeout(0), ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -335,7 +349,11 @@ def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used with httpx.Client(timeout=None) as http_client: client = ImageKit( - base_url=base_url, private_key=private_key, _strict_response_validation=True, http_client=http_client + base_url=base_url, + private_key=private_key, + password=password, + _strict_response_validation=True, + http_client=http_client, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -347,7 +365,11 @@ def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default with httpx.Client() as http_client: client = ImageKit( - base_url=base_url, private_key=private_key, _strict_response_validation=True, http_client=http_client + base_url=base_url, + private_key=private_key, + password=password, + _strict_response_validation=True, + http_client=http_client, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -359,7 +381,11 @@ def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = ImageKit( - base_url=base_url, private_key=private_key, _strict_response_validation=True, http_client=http_client + base_url=base_url, + private_key=private_key, + password=password, + _strict_response_validation=True, + http_client=http_client, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -374,6 +400,7 @@ async def test_invalid_http_client(self) -> None: ImageKit( base_url=base_url, private_key=private_key, + password=password, _strict_response_validation=True, http_client=cast(Any, http_client), ) @@ -382,6 +409,7 @@ def test_default_headers_option(self) -> None: test_client = ImageKit( base_url=base_url, private_key=private_key, + password=password, _strict_response_validation=True, default_headers={"X-Foo": "bar"}, ) @@ -392,6 +420,7 @@ def test_default_headers_option(self) -> None: test_client2 = ImageKit( base_url=base_url, private_key=private_key, + password=password, _strict_response_validation=True, default_headers={ "X-Foo": "stainless", @@ -405,10 +434,28 @@ def test_default_headers_option(self) -> None: test_client.close() test_client2.close() + def test_validate_headers(self) -> None: + client = ImageKit( + base_url=base_url, private_key=private_key, password=password, _strict_response_validation=True + ) + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + assert "Basic" in request.headers.get("Authorization") + + with pytest.raises(ImageKitError): + with update_env( + **{ + "IMAGEKIT_PRIVATE_KEY": Omit(), + "OPTIONAL_IMAGEKIT_IGNORES_THIS": Omit(), + } + ): + client2 = ImageKit(base_url=base_url, private_key=None, password=None, _strict_response_validation=True) + _ = client2 + def test_default_query_option(self) -> None: client = ImageKit( base_url=base_url, private_key=private_key, + password=password, _strict_response_validation=True, default_query={"query_param": "bar"}, ) @@ -607,6 +654,7 @@ def mock_handler(request: httpx.Request) -> httpx.Response: with ImageKit( base_url=base_url, private_key=private_key, + password=password, _strict_response_validation=True, http_client=httpx.Client(transport=MockTransport(handler=mock_handler)), ) as client: @@ -701,7 +749,10 @@ class Model(BaseModel): def test_base_url_setter(self) -> None: client = ImageKit( - base_url="https://example.com/from_init", private_key=private_key, _strict_response_validation=True + base_url="https://example.com/from_init", + private_key=private_key, + password=password, + _strict_response_validation=True, ) assert client.base_url == "https://example.com/from_init/" @@ -713,18 +764,22 @@ def test_base_url_setter(self) -> None: def test_base_url_env(self) -> None: with update_env(IMAGE_KIT_BASE_URL="http://localhost:5000/from/env"): - client = ImageKit(private_key=private_key, _strict_response_validation=True) + client = ImageKit(private_key=private_key, password=password, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @pytest.mark.parametrize( "client", [ ImageKit( - base_url="http://localhost:5000/custom/path/", private_key=private_key, _strict_response_validation=True + base_url="http://localhost:5000/custom/path/", + private_key=private_key, + password=password, + _strict_response_validation=True, ), ImageKit( base_url="http://localhost:5000/custom/path/", private_key=private_key, + password=password, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -746,11 +801,15 @@ def test_base_url_trailing_slash(self, client: ImageKit) -> None: "client", [ ImageKit( - base_url="http://localhost:5000/custom/path/", private_key=private_key, _strict_response_validation=True + base_url="http://localhost:5000/custom/path/", + private_key=private_key, + password=password, + _strict_response_validation=True, ), ImageKit( base_url="http://localhost:5000/custom/path/", private_key=private_key, + password=password, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -772,11 +831,15 @@ def test_base_url_no_trailing_slash(self, client: ImageKit) -> None: "client", [ ImageKit( - base_url="http://localhost:5000/custom/path/", private_key=private_key, _strict_response_validation=True + base_url="http://localhost:5000/custom/path/", + private_key=private_key, + password=password, + _strict_response_validation=True, ), ImageKit( base_url="http://localhost:5000/custom/path/", private_key=private_key, + password=password, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -795,7 +858,9 @@ def test_absolute_request_url(self, client: ImageKit) -> None: client.close() def test_copied_client_does_not_close_http(self) -> None: - test_client = ImageKit(base_url=base_url, private_key=private_key, _strict_response_validation=True) + test_client = ImageKit( + base_url=base_url, private_key=private_key, password=password, _strict_response_validation=True + ) assert not test_client.is_closed() copied = test_client.copy() @@ -806,7 +871,9 @@ def test_copied_client_does_not_close_http(self) -> None: assert not test_client.is_closed() def test_client_context_manager(self) -> None: - test_client = ImageKit(base_url=base_url, private_key=private_key, _strict_response_validation=True) + test_client = ImageKit( + base_url=base_url, private_key=private_key, password=password, _strict_response_validation=True + ) with test_client as c2: assert c2 is test_client assert not c2.is_closed() @@ -830,6 +897,7 @@ def test_client_max_retries_validation(self) -> None: ImageKit( base_url=base_url, private_key=private_key, + password=password, _strict_response_validation=True, max_retries=cast(Any, None), ) @@ -841,12 +909,16 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = ImageKit(base_url=base_url, private_key=private_key, _strict_response_validation=True) + strict_client = ImageKit( + base_url=base_url, private_key=private_key, password=password, _strict_response_validation=True + ) with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - non_strict_client = ImageKit(base_url=base_url, private_key=private_key, _strict_response_validation=False) + non_strict_client = ImageKit( + base_url=base_url, private_key=private_key, password=password, _strict_response_validation=False + ) response = non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -1069,6 +1141,10 @@ def test_copy(self, async_client: AsyncImageKit) -> None: assert copied.private_key == "another My Private Key" assert async_client.private_key == "My Private Key" + copied = async_client.copy(password="another My Password") + assert copied.password == "another My Password" + assert async_client.password == "My Password" + def test_copy_default_options(self, async_client: AsyncImageKit) -> None: # options that have a default are overridden correctly copied = async_client.copy(max_retries=7) @@ -1089,6 +1165,7 @@ async def test_copy_default_headers(self) -> None: client = AsyncImageKit( base_url=base_url, private_key=private_key, + password=password, _strict_response_validation=True, default_headers={"X-Foo": "bar"}, ) @@ -1125,7 +1202,11 @@ async def test_copy_default_headers(self) -> None: async def test_copy_default_query(self) -> None: client = AsyncImageKit( - base_url=base_url, private_key=private_key, _strict_response_validation=True, default_query={"foo": "bar"} + base_url=base_url, + private_key=private_key, + password=password, + _strict_response_validation=True, + default_query={"foo": "bar"}, ) assert _get_params(client)["foo"] == "bar" @@ -1253,7 +1334,11 @@ async def test_request_timeout(self, async_client: AsyncImageKit) -> None: async def test_client_timeout_option(self) -> None: client = AsyncImageKit( - base_url=base_url, private_key=private_key, _strict_response_validation=True, timeout=httpx.Timeout(0) + base_url=base_url, + private_key=private_key, + password=password, + _strict_response_validation=True, + timeout=httpx.Timeout(0), ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1266,7 +1351,11 @@ async def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used async with httpx.AsyncClient(timeout=None) as http_client: client = AsyncImageKit( - base_url=base_url, private_key=private_key, _strict_response_validation=True, http_client=http_client + base_url=base_url, + private_key=private_key, + password=password, + _strict_response_validation=True, + http_client=http_client, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1278,7 +1367,11 @@ async def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default async with httpx.AsyncClient() as http_client: client = AsyncImageKit( - base_url=base_url, private_key=private_key, _strict_response_validation=True, http_client=http_client + base_url=base_url, + private_key=private_key, + password=password, + _strict_response_validation=True, + http_client=http_client, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1290,7 +1383,11 @@ async def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = AsyncImageKit( - base_url=base_url, private_key=private_key, _strict_response_validation=True, http_client=http_client + base_url=base_url, + private_key=private_key, + password=password, + _strict_response_validation=True, + http_client=http_client, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1305,6 +1402,7 @@ def test_invalid_http_client(self) -> None: AsyncImageKit( base_url=base_url, private_key=private_key, + password=password, _strict_response_validation=True, http_client=cast(Any, http_client), ) @@ -1313,6 +1411,7 @@ async def test_default_headers_option(self) -> None: test_client = AsyncImageKit( base_url=base_url, private_key=private_key, + password=password, _strict_response_validation=True, default_headers={"X-Foo": "bar"}, ) @@ -1323,6 +1422,7 @@ async def test_default_headers_option(self) -> None: test_client2 = AsyncImageKit( base_url=base_url, private_key=private_key, + password=password, _strict_response_validation=True, default_headers={ "X-Foo": "stainless", @@ -1336,10 +1436,30 @@ async def test_default_headers_option(self) -> None: await test_client.close() await test_client2.close() + def test_validate_headers(self) -> None: + client = AsyncImageKit( + base_url=base_url, private_key=private_key, password=password, _strict_response_validation=True + ) + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + assert "Basic" in request.headers.get("Authorization") + + with pytest.raises(ImageKitError): + with update_env( + **{ + "IMAGEKIT_PRIVATE_KEY": Omit(), + "OPTIONAL_IMAGEKIT_IGNORES_THIS": Omit(), + } + ): + client2 = AsyncImageKit( + base_url=base_url, private_key=None, password=None, _strict_response_validation=True + ) + _ = client2 + async def test_default_query_option(self) -> None: client = AsyncImageKit( base_url=base_url, private_key=private_key, + password=password, _strict_response_validation=True, default_query={"query_param": "bar"}, ) @@ -1538,6 +1658,7 @@ async def mock_handler(request: httpx.Request) -> httpx.Response: async with AsyncImageKit( base_url=base_url, private_key=private_key, + password=password, _strict_response_validation=True, http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)), ) as client: @@ -1636,7 +1757,10 @@ class Model(BaseModel): async def test_base_url_setter(self) -> None: client = AsyncImageKit( - base_url="https://example.com/from_init", private_key=private_key, _strict_response_validation=True + base_url="https://example.com/from_init", + private_key=private_key, + password=password, + _strict_response_validation=True, ) assert client.base_url == "https://example.com/from_init/" @@ -1648,18 +1772,22 @@ async def test_base_url_setter(self) -> None: async def test_base_url_env(self) -> None: with update_env(IMAGE_KIT_BASE_URL="http://localhost:5000/from/env"): - client = AsyncImageKit(private_key=private_key, _strict_response_validation=True) + client = AsyncImageKit(private_key=private_key, password=password, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @pytest.mark.parametrize( "client", [ AsyncImageKit( - base_url="http://localhost:5000/custom/path/", private_key=private_key, _strict_response_validation=True + base_url="http://localhost:5000/custom/path/", + private_key=private_key, + password=password, + _strict_response_validation=True, ), AsyncImageKit( base_url="http://localhost:5000/custom/path/", private_key=private_key, + password=password, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1681,11 +1809,15 @@ async def test_base_url_trailing_slash(self, client: AsyncImageKit) -> None: "client", [ AsyncImageKit( - base_url="http://localhost:5000/custom/path/", private_key=private_key, _strict_response_validation=True + base_url="http://localhost:5000/custom/path/", + private_key=private_key, + password=password, + _strict_response_validation=True, ), AsyncImageKit( base_url="http://localhost:5000/custom/path/", private_key=private_key, + password=password, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1707,11 +1839,15 @@ async def test_base_url_no_trailing_slash(self, client: AsyncImageKit) -> None: "client", [ AsyncImageKit( - base_url="http://localhost:5000/custom/path/", private_key=private_key, _strict_response_validation=True + base_url="http://localhost:5000/custom/path/", + private_key=private_key, + password=password, + _strict_response_validation=True, ), AsyncImageKit( base_url="http://localhost:5000/custom/path/", private_key=private_key, + password=password, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1730,7 +1866,9 @@ async def test_absolute_request_url(self, client: AsyncImageKit) -> None: await client.close() async def test_copied_client_does_not_close_http(self) -> None: - test_client = AsyncImageKit(base_url=base_url, private_key=private_key, _strict_response_validation=True) + test_client = AsyncImageKit( + base_url=base_url, private_key=private_key, password=password, _strict_response_validation=True + ) assert not test_client.is_closed() copied = test_client.copy() @@ -1742,7 +1880,9 @@ async def test_copied_client_does_not_close_http(self) -> None: assert not test_client.is_closed() async def test_client_context_manager(self) -> None: - test_client = AsyncImageKit(base_url=base_url, private_key=private_key, _strict_response_validation=True) + test_client = AsyncImageKit( + base_url=base_url, private_key=private_key, password=password, _strict_response_validation=True + ) async with test_client as c2: assert c2 is test_client assert not c2.is_closed() @@ -1766,6 +1906,7 @@ async def test_client_max_retries_validation(self) -> None: AsyncImageKit( base_url=base_url, private_key=private_key, + password=password, _strict_response_validation=True, max_retries=cast(Any, None), ) @@ -1777,12 +1918,16 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = AsyncImageKit(base_url=base_url, private_key=private_key, _strict_response_validation=True) + strict_client = AsyncImageKit( + base_url=base_url, private_key=private_key, password=password, _strict_response_validation=True + ) with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - non_strict_client = AsyncImageKit(base_url=base_url, private_key=private_key, _strict_response_validation=False) + non_strict_client = AsyncImageKit( + base_url=base_url, private_key=private_key, password=password, _strict_response_validation=False + ) response = await non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] From 6ad7341af30e43252519a3c44826be408323cbbe Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 06:30:53 +0000 Subject: [PATCH 4/9] feat(api): indentation fix --- .stats.yml | 4 ++-- src/imagekitio/types/dam_file_version_create_event.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index c7e4cc5..2a0b370 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 48 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-f4cd00365ba96133e0675eae3d5d3c6ac13874789e2ce69a84310ab64a4f87dd.yml -openapi_spec_hash: dce632cfbb5464a98c0f5d8eb9573d68 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-3234424a3a5871f31f5d6dcb8173593fc6c1db14802a0e71f14f3527ad16c871.yml +openapi_spec_hash: 017a8ab68d905ed9e163022f68d8be78 config_hash: 17e408231b0b01676298010c7405f483 diff --git a/src/imagekitio/types/dam_file_version_create_event.py b/src/imagekitio/types/dam_file_version_create_event.py index 9199918..77b0979 100644 --- a/src/imagekitio/types/dam_file_version_create_event.py +++ b/src/imagekitio/types/dam_file_version_create_event.py @@ -3,6 +3,7 @@ from datetime import datetime from typing_extensions import Literal +from .file import File from .base_webhook_event import BaseWebhookEvent __all__ = ["DamFileVersionCreateEvent"] @@ -14,7 +15,8 @@ class DamFileVersionCreateEvent(BaseWebhookEvent): created_at: datetime """Timestamp of when the event occurred in ISO8601 format.""" - data: object + data: File + """Object containing details of a file or file version.""" type: Literal["file-version.created"] # type: ignore """Type of the webhook event.""" From a07e95275e50dcd975f3ec816420eee7645ce223 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 06:39:18 +0000 Subject: [PATCH 5/9] feat(api): merge with main to bring back missing parameters --- .stats.yml | 4 +- .../types/shared/extension_config.py | 12 ++- src/imagekitio/types/shared/extensions.py | 12 ++- .../types/shared/overlay_position.py | 34 +++++++- .../types/shared_params/extension_config.py | 12 ++- .../types/shared_params/extensions.py | 12 ++- .../types/shared_params/overlay_position.py | 37 ++++++++- tests/api_resources/test_dummy.py | 78 +++++++++++++++++++ 8 files changed, 182 insertions(+), 19 deletions(-) diff --git a/.stats.yml b/.stats.yml index 2a0b370..aabc411 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 48 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-3234424a3a5871f31f5d6dcb8173593fc6c1db14802a0e71f14f3527ad16c871.yml -openapi_spec_hash: 017a8ab68d905ed9e163022f68d8be78 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-18b46cb8c1dd5cd0eea8559fa9671600540c5c4bee32f2d74f932416b7a1aee0.yml +openapi_spec_hash: 539770659847d04a92ef965a5313adde config_hash: 17e408231b0b01676298010c7405f483 diff --git a/src/imagekitio/types/shared/extension_config.py b/src/imagekitio/types/shared/extension_config.py index 5cc15e9..671e0ac 100644 --- a/src/imagekitio/types/shared/extension_config.py +++ b/src/imagekitio/types/shared/extension_config.py @@ -99,8 +99,9 @@ class AITasksTaskSelectTags(BaseModel): vocabulary: Optional[List[str]] = None """Array of possible tag values. - Combined length of all strings must not exceed 500 characters. Cannot contain - the `%` character. + The combined length of all strings must not exceed 500 characters, and values + cannot include the `%` character. When providing large vocabularies (more than + 30 items), the AI may not follow the list strictly. """ @@ -124,7 +125,12 @@ class AITasksTaskSelectMetadata(BaseModel): """Minimum number of values to select from the vocabulary.""" vocabulary: Optional[List[Union[str, float, bool]]] = None - """Array of possible values matching the custom metadata field type.""" + """An array of possible values matching the custom metadata field type. + + If not provided for SingleSelect or MultiSelect field types, all values from the + custom metadata field definition will be used. When providing large vocabularies + (above 30 items), the AI may not strictly adhere to the list. + """ class AITasksTaskYesNoOnNoSetMetadata(BaseModel): diff --git a/src/imagekitio/types/shared/extensions.py b/src/imagekitio/types/shared/extensions.py index 94f132c..c680a09 100644 --- a/src/imagekitio/types/shared/extensions.py +++ b/src/imagekitio/types/shared/extensions.py @@ -101,8 +101,9 @@ class ExtensionItemAITasksTaskSelectTags(BaseModel): vocabulary: Optional[List[str]] = None """Array of possible tag values. - Combined length of all strings must not exceed 500 characters. Cannot contain - the `%` character. + The combined length of all strings must not exceed 500 characters, and values + cannot include the `%` character. When providing large vocabularies (more than + 30 items), the AI may not follow the list strictly. """ @@ -126,7 +127,12 @@ class ExtensionItemAITasksTaskSelectMetadata(BaseModel): """Minimum number of values to select from the vocabulary.""" vocabulary: Optional[List[Union[str, float, bool]]] = None - """Array of possible values matching the custom metadata field type.""" + """An array of possible values matching the custom metadata field type. + + If not provided for SingleSelect or MultiSelect field types, all values from the + custom metadata field definition will be used. When providing large vocabularies + (above 30 items), the AI may not strictly adhere to the list. + """ class ExtensionItemAITasksTaskYesNoOnNoSetMetadata(BaseModel): diff --git a/src/imagekitio/types/shared/overlay_position.py b/src/imagekitio/types/shared/overlay_position.py index a6fe5f8..f6cc75c 100644 --- a/src/imagekitio/types/shared/overlay_position.py +++ b/src/imagekitio/types/shared/overlay_position.py @@ -3,18 +3,30 @@ from typing import Union, Optional from typing_extensions import Literal +from pydantic import Field as FieldInfo + from ..._models import BaseModel __all__ = ["OverlayPosition"] class OverlayPosition(BaseModel): + anchor_point: Optional[ + Literal["top", "left", "right", "bottom", "top_left", "top_right", "bottom_left", "bottom_right", "center"] + ] = FieldInfo(alias="anchorPoint", default=None) + """ + Sets the anchor point on the base asset from which the overlay offset is + calculated. The default value is `top_left`. Maps to `lap` in the URL. Can only + be used with one or more of `x`, `y`, `xCenter`, or `yCenter`. + """ + focus: Optional[ Literal["center", "top", "left", "bottom", "right", "top_left", "top_right", "bottom_left", "bottom_right"] ] = None """ - Specifies the position of the overlay relative to the parent image or video. - Maps to `lfo` in the URL. + Specifies the position of the overlay relative to the parent image or video. If + one or more of `x`, `y`, `xCenter`, or `yCenter` parameters are specified, this + parameter is ignored. Maps to `lfo` in the URL. """ x: Union[float, str, None] = None @@ -26,6 +38,15 @@ class OverlayPosition(BaseModel): [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). """ + x_center: Union[float, str, None] = FieldInfo(alias="xCenter", default=None) + """ + Specifies the x-coordinate on the base asset where the overlay's center will be + positioned. It also accepts arithmetic expressions such as `bw_mul_0.4` or + `bw_sub_cw`. Maps to `lxc` in the URL. Cannot be used together with `x`, but can + be used with `y`. Learn about + [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). + """ + y: Union[float, str, None] = None """ Specifies the y-coordinate of the top-left corner of the base asset where the @@ -34,3 +55,12 @@ class OverlayPosition(BaseModel): about [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). """ + + y_center: Union[float, str, None] = FieldInfo(alias="yCenter", default=None) + """ + Specifies the y-coordinate on the base asset where the overlay's center will be + positioned. It also accepts arithmetic expressions such as `bh_mul_0.4` or + `bh_sub_ch`. Maps to `lyc` in the URL. Cannot be used together with `y`, but can + be used with `x`. Learn about + [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). + """ diff --git a/src/imagekitio/types/shared_params/extension_config.py b/src/imagekitio/types/shared_params/extension_config.py index 453fb73..78458ad 100644 --- a/src/imagekitio/types/shared_params/extension_config.py +++ b/src/imagekitio/types/shared_params/extension_config.py @@ -99,8 +99,9 @@ class AITasksTaskSelectTags(TypedDict, total=False): vocabulary: SequenceNotStr[str] """Array of possible tag values. - Combined length of all strings must not exceed 500 characters. Cannot contain - the `%` character. + The combined length of all strings must not exceed 500 characters, and values + cannot include the `%` character. When providing large vocabularies (more than + 30 items), the AI may not follow the list strictly. """ @@ -124,7 +125,12 @@ class AITasksTaskSelectMetadata(TypedDict, total=False): """Minimum number of values to select from the vocabulary.""" vocabulary: SequenceNotStr[Union[str, float, bool]] - """Array of possible values matching the custom metadata field type.""" + """An array of possible values matching the custom metadata field type. + + If not provided for SingleSelect or MultiSelect field types, all values from the + custom metadata field definition will be used. When providing large vocabularies + (above 30 items), the AI may not strictly adhere to the list. + """ class AITasksTaskYesNoOnNoSetMetadata(TypedDict, total=False): diff --git a/src/imagekitio/types/shared_params/extensions.py b/src/imagekitio/types/shared_params/extensions.py index f4c8149..176b80e 100644 --- a/src/imagekitio/types/shared_params/extensions.py +++ b/src/imagekitio/types/shared_params/extensions.py @@ -101,8 +101,9 @@ class ExtensionItemAITasksTaskSelectTags(TypedDict, total=False): vocabulary: SequenceNotStr[str] """Array of possible tag values. - Combined length of all strings must not exceed 500 characters. Cannot contain - the `%` character. + The combined length of all strings must not exceed 500 characters, and values + cannot include the `%` character. When providing large vocabularies (more than + 30 items), the AI may not follow the list strictly. """ @@ -126,7 +127,12 @@ class ExtensionItemAITasksTaskSelectMetadata(TypedDict, total=False): """Minimum number of values to select from the vocabulary.""" vocabulary: SequenceNotStr[Union[str, float, bool]] - """Array of possible values matching the custom metadata field type.""" + """An array of possible values matching the custom metadata field type. + + If not provided for SingleSelect or MultiSelect field types, all values from the + custom metadata field definition will be used. When providing large vocabularies + (above 30 items), the AI may not strictly adhere to the list. + """ class ExtensionItemAITasksTaskYesNoOnNoSetMetadata(TypedDict, total=False): diff --git a/src/imagekitio/types/shared_params/overlay_position.py b/src/imagekitio/types/shared_params/overlay_position.py index f74e3e1..2af7068 100644 --- a/src/imagekitio/types/shared_params/overlay_position.py +++ b/src/imagekitio/types/shared_params/overlay_position.py @@ -3,16 +3,29 @@ from __future__ import annotations from typing import Union -from typing_extensions import Literal, TypedDict +from typing_extensions import Literal, Annotated, TypedDict + +from ..._utils import PropertyInfo __all__ = ["OverlayPosition"] class OverlayPosition(TypedDict, total=False): + anchor_point: Annotated[ + Literal["top", "left", "right", "bottom", "top_left", "top_right", "bottom_left", "bottom_right", "center"], + PropertyInfo(alias="anchorPoint"), + ] + """ + Sets the anchor point on the base asset from which the overlay offset is + calculated. The default value is `top_left`. Maps to `lap` in the URL. Can only + be used with one or more of `x`, `y`, `xCenter`, or `yCenter`. + """ + focus: Literal["center", "top", "left", "bottom", "right", "top_left", "top_right", "bottom_left", "bottom_right"] """ - Specifies the position of the overlay relative to the parent image or video. - Maps to `lfo` in the URL. + Specifies the position of the overlay relative to the parent image or video. If + one or more of `x`, `y`, `xCenter`, or `yCenter` parameters are specified, this + parameter is ignored. Maps to `lfo` in the URL. """ x: Union[float, str] @@ -24,6 +37,15 @@ class OverlayPosition(TypedDict, total=False): [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). """ + x_center: Annotated[Union[float, str], PropertyInfo(alias="xCenter")] + """ + Specifies the x-coordinate on the base asset where the overlay's center will be + positioned. It also accepts arithmetic expressions such as `bw_mul_0.4` or + `bw_sub_cw`. Maps to `lxc` in the URL. Cannot be used together with `x`, but can + be used with `y`. Learn about + [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). + """ + y: Union[float, str] """ Specifies the y-coordinate of the top-left corner of the base asset where the @@ -32,3 +54,12 @@ class OverlayPosition(TypedDict, total=False): about [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). """ + + y_center: Annotated[Union[float, str], PropertyInfo(alias="yCenter")] + """ + Specifies the y-coordinate on the base asset where the overlay's center will be + positioned. It also accepts arithmetic expressions such as `bh_mul_0.4` or + `bh_sub_ch`. Maps to `lyc` in the URL. Cannot be used together with `y`, but can + be used with `x`. Learn about + [Arithmetic expressions](https://imagekit.io/docs/arithmetic-expressions-in-transformations). + """ diff --git a/tests/api_resources/test_dummy.py b/tests/api_resources/test_dummy.py index 11b63ba..655d6c5 100644 --- a/tests/api_resources/test_dummy.py +++ b/tests/api_resources/test_dummy.py @@ -29,9 +29,12 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: base_overlay={ "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -163,9 +166,12 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: "overlay": { "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -223,9 +229,12 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: image_overlay={ "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -274,9 +283,12 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: "overlay": { "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -329,9 +341,12 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: overlay={ "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -360,9 +375,12 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: ], }, overlay_position={ + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, overlay_timing={ "duration": 0, @@ -394,9 +412,12 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: solid_color_overlay={ "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -469,9 +490,12 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: "overlay": { "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -526,9 +550,12 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: subtitle_overlay={ "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -562,9 +589,12 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: text_overlay={ "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -645,9 +675,12 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: "overlay": { "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -699,9 +732,12 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: video_overlay={ "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -750,9 +786,12 @@ def test_method_create_with_all_params(self, client: ImageKit) -> None: "overlay": { "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -846,9 +885,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) base_overlay={ "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -980,9 +1022,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) "overlay": { "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -1040,9 +1085,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) image_overlay={ "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -1091,9 +1139,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) "overlay": { "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -1146,9 +1197,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) overlay={ "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -1177,9 +1231,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) ], }, overlay_position={ + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, overlay_timing={ "duration": 0, @@ -1211,9 +1268,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) solid_color_overlay={ "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -1286,9 +1346,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) "overlay": { "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -1343,9 +1406,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) subtitle_overlay={ "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -1379,9 +1445,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) text_overlay={ "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -1462,9 +1531,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) "overlay": { "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -1516,9 +1588,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) video_overlay={ "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, @@ -1567,9 +1642,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncImageKit) "overlay": { "layer_mode": "multiply", "position": { + "anchor_point": "top", "focus": "center", "x": 0, + "x_center": 0, "y": 0, + "y_center": 0, }, "timing": { "duration": 0, From bf9e082da50cea2f983b5bd88caca825e5039ec5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 06:55:00 +0000 Subject: [PATCH 6/9] feat(api): update webhook event names and remove DAM prefix --- .stats.yml | 4 +-- api.md | 15 ++++++--- src/imagekitio/types/__init__.py | 10 +++--- ...event.py => file_created_webhook_event.py} | 4 +-- .../types/file_created_webhook_event1.py | 22 +++++++++++++ ...event.py => file_deleted_webhook_event.py} | 8 ++--- .../types/file_deleted_webhook_event1.py | 28 +++++++++++++++++ ...event.py => file_updated_webhook_event.py} | 4 +-- .../types/file_updated_webhook_event1.py | 22 +++++++++++++ ... => file_version_created_webhook_event.py} | 4 +-- .../file_version_created_webhook_event1.py | 22 +++++++++++++ ... => file_version_deleted_webhook_event.py} | 8 ++--- .../file_version_deleted_webhook_event1.py | 31 +++++++++++++++++++ .../types/unsafe_unwrap_webhook_event.py | 20 ++++++------ src/imagekitio/types/unwrap_webhook_event.py | 20 ++++++------ 15 files changed, 176 insertions(+), 46 deletions(-) rename src/imagekitio/types/{dam_file_create_event.py => file_created_webhook_event.py} (85%) create mode 100644 src/imagekitio/types/file_created_webhook_event1.py rename src/imagekitio/types/{dam_file_delete_event.py => file_deleted_webhook_event.py} (74%) create mode 100644 src/imagekitio/types/file_deleted_webhook_event1.py rename src/imagekitio/types/{dam_file_update_event.py => file_updated_webhook_event.py} (85%) create mode 100644 src/imagekitio/types/file_updated_webhook_event1.py rename src/imagekitio/types/{dam_file_version_create_event.py => file_version_created_webhook_event.py} (84%) create mode 100644 src/imagekitio/types/file_version_created_webhook_event1.py rename src/imagekitio/types/{dam_file_version_delete_event.py => file_version_deleted_webhook_event.py} (75%) create mode 100644 src/imagekitio/types/file_version_deleted_webhook_event1.py diff --git a/.stats.yml b/.stats.yml index aabc411..447f914 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 48 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-18b46cb8c1dd5cd0eea8559fa9671600540c5c4bee32f2d74f932416b7a1aee0.yml -openapi_spec_hash: 539770659847d04a92ef965a5313adde +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-d73a37dc3426586109bd153f02c6a605036b6a7396bba5173d013468c5291ce6.yml +openapi_spec_hash: c193c6e557ff477481ec8d5ac8a0c96e config_hash: 17e408231b0b01676298010c7405f483 diff --git a/api.md b/api.md index c3dac75..dd55354 100644 --- a/api.md +++ b/api.md @@ -265,11 +265,6 @@ Types: ```python from imagekitio.types import ( BaseWebhookEvent, - DamFileCreateEvent, - DamFileDeleteEvent, - DamFileUpdateEvent, - DamFileVersionCreateEvent, - DamFileVersionDeleteEvent, UploadPostTransformErrorEvent, UploadPostTransformSuccessEvent, UploadPreTransformErrorEvent, @@ -277,6 +272,16 @@ from imagekitio.types import ( VideoTransformationAcceptedEvent, VideoTransformationErrorEvent, VideoTransformationReadyEvent, + FileCreatedWebhookEvent, + FileUpdatedWebhookEvent, + FileDeletedWebhookEvent, + FileVersionCreatedWebhookEvent, + FileVersionDeletedWebhookEvent, + FileCreatedWebhookEvent, + FileUpdatedWebhookEvent, + FileDeletedWebhookEvent, + FileVersionCreatedWebhookEvent, + FileVersionDeletedWebhookEvent, UnsafeUnwrapWebhookEvent, UnwrapWebhookEvent, ) diff --git a/src/imagekitio/types/__init__.py b/src/imagekitio/types/__init__.py index 0adca51..df93c61 100644 --- a/src/imagekitio/types/__init__.py +++ b/src/imagekitio/types/__init__.py @@ -53,16 +53,14 @@ from .folder_rename_params import FolderRenameParams as FolderRenameParams from .unwrap_webhook_event import UnwrapWebhookEvent as UnwrapWebhookEvent from .custom_metadata_field import CustomMetadataField as CustomMetadataField -from .dam_file_create_event import DamFileCreateEvent as DamFileCreateEvent -from .dam_file_delete_event import DamFileDeleteEvent as DamFileDeleteEvent -from .dam_file_update_event import DamFileUpdateEvent as DamFileUpdateEvent from .folder_create_response import FolderCreateResponse as FolderCreateResponse from .folder_delete_response import FolderDeleteResponse as FolderDeleteResponse from .folder_rename_response import FolderRenameResponse as FolderRenameResponse from .update_file_request_param import UpdateFileRequestParam as UpdateFileRequestParam +from .file_created_webhook_event import FileCreatedWebhookEvent as FileCreatedWebhookEvent +from .file_deleted_webhook_event import FileDeletedWebhookEvent as FileDeletedWebhookEvent +from .file_updated_webhook_event import FileUpdatedWebhookEvent as FileUpdatedWebhookEvent from .unsafe_unwrap_webhook_event import UnsafeUnwrapWebhookEvent as UnsafeUnwrapWebhookEvent -from .dam_file_version_create_event import DamFileVersionCreateEvent as DamFileVersionCreateEvent -from .dam_file_version_delete_event import DamFileVersionDeleteEvent as DamFileVersionDeleteEvent from .saved_extension_create_params import SavedExtensionCreateParams as SavedExtensionCreateParams from .saved_extension_list_response import SavedExtensionListResponse as SavedExtensionListResponse from .saved_extension_update_params import SavedExtensionUpdateParams as SavedExtensionUpdateParams @@ -71,6 +69,8 @@ from .video_transformation_ready_event import VideoTransformationReadyEvent as VideoTransformationReadyEvent from .custom_metadata_field_list_params import CustomMetadataFieldListParams as CustomMetadataFieldListParams from .upload_post_transform_error_event import UploadPostTransformErrorEvent as UploadPostTransformErrorEvent +from .file_version_created_webhook_event import FileVersionCreatedWebhookEvent as FileVersionCreatedWebhookEvent +from .file_version_deleted_webhook_event import FileVersionDeletedWebhookEvent as FileVersionDeletedWebhookEvent from .upload_pre_transform_success_event import UploadPreTransformSuccessEvent as UploadPreTransformSuccessEvent from .custom_metadata_field_create_params import CustomMetadataFieldCreateParams as CustomMetadataFieldCreateParams from .custom_metadata_field_list_response import CustomMetadataFieldListResponse as CustomMetadataFieldListResponse diff --git a/src/imagekitio/types/dam_file_create_event.py b/src/imagekitio/types/file_created_webhook_event.py similarity index 85% rename from src/imagekitio/types/dam_file_create_event.py rename to src/imagekitio/types/file_created_webhook_event.py index cc4388a..3500941 100644 --- a/src/imagekitio/types/dam_file_create_event.py +++ b/src/imagekitio/types/file_created_webhook_event.py @@ -6,10 +6,10 @@ from .file import File from .base_webhook_event import BaseWebhookEvent -__all__ = ["DamFileCreateEvent"] +__all__ = ["FileCreatedWebhookEvent"] -class DamFileCreateEvent(BaseWebhookEvent): +class FileCreatedWebhookEvent(BaseWebhookEvent): """Triggered when a file is created.""" created_at: datetime diff --git a/src/imagekitio/types/file_created_webhook_event1.py b/src/imagekitio/types/file_created_webhook_event1.py new file mode 100644 index 0000000..3500941 --- /dev/null +++ b/src/imagekitio/types/file_created_webhook_event1.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .file import File +from .base_webhook_event import BaseWebhookEvent + +__all__ = ["FileCreatedWebhookEvent"] + + +class FileCreatedWebhookEvent(BaseWebhookEvent): + """Triggered when a file is created.""" + + created_at: datetime + """Timestamp of when the event occurred in ISO8601 format.""" + + data: File + """Object containing details of a file or file version.""" + + type: Literal["file.created"] # type: ignore + """Type of the webhook event.""" diff --git a/src/imagekitio/types/dam_file_delete_event.py b/src/imagekitio/types/file_deleted_webhook_event.py similarity index 74% rename from src/imagekitio/types/dam_file_delete_event.py rename to src/imagekitio/types/file_deleted_webhook_event.py index ef32762..4ec9bbe 100644 --- a/src/imagekitio/types/dam_file_delete_event.py +++ b/src/imagekitio/types/file_deleted_webhook_event.py @@ -8,21 +8,21 @@ from .._models import BaseModel from .base_webhook_event import BaseWebhookEvent -__all__ = ["DamFileDeleteEvent", "DamFileDeleteEventData"] +__all__ = ["FileDeletedWebhookEvent", "FileDeletedWebhookEventData"] -class DamFileDeleteEventData(BaseModel): +class FileDeletedWebhookEventData(BaseModel): file_id: str = FieldInfo(alias="fileId") """The unique `fileId` of the deleted file.""" -class DamFileDeleteEvent(BaseWebhookEvent): +class FileDeletedWebhookEvent(BaseWebhookEvent): """Triggered when a file is deleted.""" created_at: datetime """Timestamp of when the event occurred in ISO8601 format.""" - data: DamFileDeleteEventData + data: FileDeletedWebhookEventData type: Literal["file.deleted"] # type: ignore """Type of the webhook event.""" diff --git a/src/imagekitio/types/file_deleted_webhook_event1.py b/src/imagekitio/types/file_deleted_webhook_event1.py new file mode 100644 index 0000000..4ec9bbe --- /dev/null +++ b/src/imagekitio/types/file_deleted_webhook_event1.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .base_webhook_event import BaseWebhookEvent + +__all__ = ["FileDeletedWebhookEvent", "FileDeletedWebhookEventData"] + + +class FileDeletedWebhookEventData(BaseModel): + file_id: str = FieldInfo(alias="fileId") + """The unique `fileId` of the deleted file.""" + + +class FileDeletedWebhookEvent(BaseWebhookEvent): + """Triggered when a file is deleted.""" + + created_at: datetime + """Timestamp of when the event occurred in ISO8601 format.""" + + data: FileDeletedWebhookEventData + + type: Literal["file.deleted"] # type: ignore + """Type of the webhook event.""" diff --git a/src/imagekitio/types/dam_file_update_event.py b/src/imagekitio/types/file_updated_webhook_event.py similarity index 85% rename from src/imagekitio/types/dam_file_update_event.py rename to src/imagekitio/types/file_updated_webhook_event.py index 77b4d7c..397742b 100644 --- a/src/imagekitio/types/dam_file_update_event.py +++ b/src/imagekitio/types/file_updated_webhook_event.py @@ -6,10 +6,10 @@ from .file import File from .base_webhook_event import BaseWebhookEvent -__all__ = ["DamFileUpdateEvent"] +__all__ = ["FileUpdatedWebhookEvent"] -class DamFileUpdateEvent(BaseWebhookEvent): +class FileUpdatedWebhookEvent(BaseWebhookEvent): """Triggered when a file is updated.""" created_at: datetime diff --git a/src/imagekitio/types/file_updated_webhook_event1.py b/src/imagekitio/types/file_updated_webhook_event1.py new file mode 100644 index 0000000..397742b --- /dev/null +++ b/src/imagekitio/types/file_updated_webhook_event1.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .file import File +from .base_webhook_event import BaseWebhookEvent + +__all__ = ["FileUpdatedWebhookEvent"] + + +class FileUpdatedWebhookEvent(BaseWebhookEvent): + """Triggered when a file is updated.""" + + created_at: datetime + """Timestamp of when the event occurred in ISO8601 format.""" + + data: File + """Object containing details of a file or file version.""" + + type: Literal["file.updated"] # type: ignore + """Type of the webhook event.""" diff --git a/src/imagekitio/types/dam_file_version_create_event.py b/src/imagekitio/types/file_version_created_webhook_event.py similarity index 84% rename from src/imagekitio/types/dam_file_version_create_event.py rename to src/imagekitio/types/file_version_created_webhook_event.py index 77b0979..242a433 100644 --- a/src/imagekitio/types/dam_file_version_create_event.py +++ b/src/imagekitio/types/file_version_created_webhook_event.py @@ -6,10 +6,10 @@ from .file import File from .base_webhook_event import BaseWebhookEvent -__all__ = ["DamFileVersionCreateEvent"] +__all__ = ["FileVersionCreatedWebhookEvent"] -class DamFileVersionCreateEvent(BaseWebhookEvent): +class FileVersionCreatedWebhookEvent(BaseWebhookEvent): """Triggered when a file version is created.""" created_at: datetime diff --git a/src/imagekitio/types/file_version_created_webhook_event1.py b/src/imagekitio/types/file_version_created_webhook_event1.py new file mode 100644 index 0000000..242a433 --- /dev/null +++ b/src/imagekitio/types/file_version_created_webhook_event1.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .file import File +from .base_webhook_event import BaseWebhookEvent + +__all__ = ["FileVersionCreatedWebhookEvent"] + + +class FileVersionCreatedWebhookEvent(BaseWebhookEvent): + """Triggered when a file version is created.""" + + created_at: datetime + """Timestamp of when the event occurred in ISO8601 format.""" + + data: File + """Object containing details of a file or file version.""" + + type: Literal["file-version.created"] # type: ignore + """Type of the webhook event.""" diff --git a/src/imagekitio/types/dam_file_version_delete_event.py b/src/imagekitio/types/file_version_deleted_webhook_event.py similarity index 75% rename from src/imagekitio/types/dam_file_version_delete_event.py rename to src/imagekitio/types/file_version_deleted_webhook_event.py index 267422a..653087a 100644 --- a/src/imagekitio/types/dam_file_version_delete_event.py +++ b/src/imagekitio/types/file_version_deleted_webhook_event.py @@ -8,10 +8,10 @@ from .._models import BaseModel from .base_webhook_event import BaseWebhookEvent -__all__ = ["DamFileVersionDeleteEvent", "DamFileVersionDeleteEventData"] +__all__ = ["FileVersionDeletedWebhookEvent", "FileVersionDeletedWebhookEventData"] -class DamFileVersionDeleteEventData(BaseModel): +class FileVersionDeletedWebhookEventData(BaseModel): file_id: str = FieldInfo(alias="fileId") """The unique `fileId` of the deleted file.""" @@ -19,13 +19,13 @@ class DamFileVersionDeleteEventData(BaseModel): """The unique `versionId` of the deleted file version.""" -class DamFileVersionDeleteEvent(BaseWebhookEvent): +class FileVersionDeletedWebhookEvent(BaseWebhookEvent): """Triggered when a file version is deleted.""" created_at: datetime """Timestamp of when the event occurred in ISO8601 format.""" - data: DamFileVersionDeleteEventData + data: FileVersionDeletedWebhookEventData type: Literal["file-version.deleted"] # type: ignore """Type of the webhook event.""" diff --git a/src/imagekitio/types/file_version_deleted_webhook_event1.py b/src/imagekitio/types/file_version_deleted_webhook_event1.py new file mode 100644 index 0000000..653087a --- /dev/null +++ b/src/imagekitio/types/file_version_deleted_webhook_event1.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .base_webhook_event import BaseWebhookEvent + +__all__ = ["FileVersionDeletedWebhookEvent", "FileVersionDeletedWebhookEventData"] + + +class FileVersionDeletedWebhookEventData(BaseModel): + file_id: str = FieldInfo(alias="fileId") + """The unique `fileId` of the deleted file.""" + + version_id: str = FieldInfo(alias="versionId") + """The unique `versionId` of the deleted file version.""" + + +class FileVersionDeletedWebhookEvent(BaseWebhookEvent): + """Triggered when a file version is deleted.""" + + created_at: datetime + """Timestamp of when the event occurred in ISO8601 format.""" + + data: FileVersionDeletedWebhookEventData + + type: Literal["file-version.deleted"] # type: ignore + """Type of the webhook event.""" diff --git a/src/imagekitio/types/unsafe_unwrap_webhook_event.py b/src/imagekitio/types/unsafe_unwrap_webhook_event.py index 95ed5cf..37f0a56 100644 --- a/src/imagekitio/types/unsafe_unwrap_webhook_event.py +++ b/src/imagekitio/types/unsafe_unwrap_webhook_event.py @@ -4,15 +4,15 @@ from typing_extensions import Annotated, TypeAlias from .._utils import PropertyInfo -from .dam_file_create_event import DamFileCreateEvent -from .dam_file_delete_event import DamFileDeleteEvent -from .dam_file_update_event import DamFileUpdateEvent -from .dam_file_version_create_event import DamFileVersionCreateEvent -from .dam_file_version_delete_event import DamFileVersionDeleteEvent +from .file_created_webhook_event import FileCreatedWebhookEvent +from .file_deleted_webhook_event import FileDeletedWebhookEvent +from .file_updated_webhook_event import FileUpdatedWebhookEvent from .upload_pre_transform_error_event import UploadPreTransformErrorEvent from .video_transformation_error_event import VideoTransformationErrorEvent from .video_transformation_ready_event import VideoTransformationReadyEvent from .upload_post_transform_error_event import UploadPostTransformErrorEvent +from .file_version_created_webhook_event import FileVersionCreatedWebhookEvent +from .file_version_deleted_webhook_event import FileVersionDeletedWebhookEvent from .upload_pre_transform_success_event import UploadPreTransformSuccessEvent from .upload_post_transform_success_event import UploadPostTransformSuccessEvent from .video_transformation_accepted_event import VideoTransformationAcceptedEvent @@ -28,11 +28,11 @@ UploadPreTransformErrorEvent, UploadPostTransformSuccessEvent, UploadPostTransformErrorEvent, - DamFileCreateEvent, - DamFileUpdateEvent, - DamFileDeleteEvent, - DamFileVersionCreateEvent, - DamFileVersionDeleteEvent, + FileCreatedWebhookEvent, + FileUpdatedWebhookEvent, + FileDeletedWebhookEvent, + FileVersionCreatedWebhookEvent, + FileVersionDeletedWebhookEvent, ], PropertyInfo(discriminator="type"), ] diff --git a/src/imagekitio/types/unwrap_webhook_event.py b/src/imagekitio/types/unwrap_webhook_event.py index 5d87d50..de16986 100644 --- a/src/imagekitio/types/unwrap_webhook_event.py +++ b/src/imagekitio/types/unwrap_webhook_event.py @@ -4,15 +4,15 @@ from typing_extensions import Annotated, TypeAlias from .._utils import PropertyInfo -from .dam_file_create_event import DamFileCreateEvent -from .dam_file_delete_event import DamFileDeleteEvent -from .dam_file_update_event import DamFileUpdateEvent -from .dam_file_version_create_event import DamFileVersionCreateEvent -from .dam_file_version_delete_event import DamFileVersionDeleteEvent +from .file_created_webhook_event import FileCreatedWebhookEvent +from .file_deleted_webhook_event import FileDeletedWebhookEvent +from .file_updated_webhook_event import FileUpdatedWebhookEvent from .upload_pre_transform_error_event import UploadPreTransformErrorEvent from .video_transformation_error_event import VideoTransformationErrorEvent from .video_transformation_ready_event import VideoTransformationReadyEvent from .upload_post_transform_error_event import UploadPostTransformErrorEvent +from .file_version_created_webhook_event import FileVersionCreatedWebhookEvent +from .file_version_deleted_webhook_event import FileVersionDeletedWebhookEvent from .upload_pre_transform_success_event import UploadPreTransformSuccessEvent from .upload_post_transform_success_event import UploadPostTransformSuccessEvent from .video_transformation_accepted_event import VideoTransformationAcceptedEvent @@ -28,11 +28,11 @@ UploadPreTransformErrorEvent, UploadPostTransformSuccessEvent, UploadPostTransformErrorEvent, - DamFileCreateEvent, - DamFileUpdateEvent, - DamFileDeleteEvent, - DamFileVersionCreateEvent, - DamFileVersionDeleteEvent, + FileCreatedWebhookEvent, + FileUpdatedWebhookEvent, + FileDeletedWebhookEvent, + FileVersionCreatedWebhookEvent, + FileVersionDeletedWebhookEvent, ], PropertyInfo(discriminator="type"), ] From 16b113f1e6f42b4ac1af43c4cf0567cae55f6ecf Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 06:59:27 +0000 Subject: [PATCH 7/9] fix(api): rename DamFile events to File for consistency --- .stats.yml | 2 +- api.md | 15 +++------ src/imagekitio/types/__init__.py | 10 +++--- ..._webhook_event.py => file_create_event.py} | 4 +-- .../types/file_created_webhook_event1.py | 22 ------------- ..._webhook_event.py => file_delete_event.py} | 8 ++--- .../types/file_deleted_webhook_event1.py | 28 ----------------- ..._webhook_event.py => file_update_event.py} | 4 +-- .../types/file_updated_webhook_event1.py | 22 ------------- ..._event.py => file_version_create_event.py} | 4 +-- .../file_version_created_webhook_event1.py | 22 ------------- ..._event.py => file_version_delete_event.py} | 8 ++--- .../file_version_deleted_webhook_event1.py | 31 ------------------- .../types/unsafe_unwrap_webhook_event.py | 20 ++++++------ src/imagekitio/types/unwrap_webhook_event.py | 20 ++++++------ 15 files changed, 45 insertions(+), 175 deletions(-) rename src/imagekitio/types/{file_created_webhook_event.py => file_create_event.py} (85%) delete mode 100644 src/imagekitio/types/file_created_webhook_event1.py rename src/imagekitio/types/{file_deleted_webhook_event.py => file_delete_event.py} (74%) delete mode 100644 src/imagekitio/types/file_deleted_webhook_event1.py rename src/imagekitio/types/{file_updated_webhook_event.py => file_update_event.py} (85%) delete mode 100644 src/imagekitio/types/file_updated_webhook_event1.py rename src/imagekitio/types/{file_version_created_webhook_event.py => file_version_create_event.py} (84%) delete mode 100644 src/imagekitio/types/file_version_created_webhook_event1.py rename src/imagekitio/types/{file_version_deleted_webhook_event.py => file_version_delete_event.py} (75%) delete mode 100644 src/imagekitio/types/file_version_deleted_webhook_event1.py diff --git a/.stats.yml b/.stats.yml index 447f914..d2c9acd 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 48 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-d73a37dc3426586109bd153f02c6a605036b6a7396bba5173d013468c5291ce6.yml openapi_spec_hash: c193c6e557ff477481ec8d5ac8a0c96e -config_hash: 17e408231b0b01676298010c7405f483 +config_hash: 32b155378f65c234d3abeb18519fb3cd diff --git a/api.md b/api.md index dd55354..a331b68 100644 --- a/api.md +++ b/api.md @@ -265,6 +265,11 @@ Types: ```python from imagekitio.types import ( BaseWebhookEvent, + FileCreateEvent, + FileDeleteEvent, + FileUpdateEvent, + FileVersionCreateEvent, + FileVersionDeleteEvent, UploadPostTransformErrorEvent, UploadPostTransformSuccessEvent, UploadPreTransformErrorEvent, @@ -272,16 +277,6 @@ from imagekitio.types import ( VideoTransformationAcceptedEvent, VideoTransformationErrorEvent, VideoTransformationReadyEvent, - FileCreatedWebhookEvent, - FileUpdatedWebhookEvent, - FileDeletedWebhookEvent, - FileVersionCreatedWebhookEvent, - FileVersionDeletedWebhookEvent, - FileCreatedWebhookEvent, - FileUpdatedWebhookEvent, - FileDeletedWebhookEvent, - FileVersionCreatedWebhookEvent, - FileVersionDeletedWebhookEvent, UnsafeUnwrapWebhookEvent, UnwrapWebhookEvent, ) diff --git a/src/imagekitio/types/__init__.py b/src/imagekitio/types/__init__.py index df93c61..c5d8520 100644 --- a/src/imagekitio/types/__init__.py +++ b/src/imagekitio/types/__init__.py @@ -33,6 +33,9 @@ from .file_copy_params import FileCopyParams as FileCopyParams from .file_move_params import FileMoveParams as FileMoveParams from .asset_list_params import AssetListParams as AssetListParams +from .file_create_event import FileCreateEvent as FileCreateEvent +from .file_delete_event import FileDeleteEvent as FileDeleteEvent +from .file_update_event import FileUpdateEvent as FileUpdateEvent from .base_webhook_event import BaseWebhookEvent as BaseWebhookEvent from .file_copy_response import FileCopyResponse as FileCopyResponse from .file_move_response import FileMoveResponse as FileMoveResponse @@ -56,10 +59,9 @@ from .folder_create_response import FolderCreateResponse as FolderCreateResponse from .folder_delete_response import FolderDeleteResponse as FolderDeleteResponse from .folder_rename_response import FolderRenameResponse as FolderRenameResponse +from .file_version_create_event import FileVersionCreateEvent as FileVersionCreateEvent +from .file_version_delete_event import FileVersionDeleteEvent as FileVersionDeleteEvent from .update_file_request_param import UpdateFileRequestParam as UpdateFileRequestParam -from .file_created_webhook_event import FileCreatedWebhookEvent as FileCreatedWebhookEvent -from .file_deleted_webhook_event import FileDeletedWebhookEvent as FileDeletedWebhookEvent -from .file_updated_webhook_event import FileUpdatedWebhookEvent as FileUpdatedWebhookEvent from .unsafe_unwrap_webhook_event import UnsafeUnwrapWebhookEvent as UnsafeUnwrapWebhookEvent from .saved_extension_create_params import SavedExtensionCreateParams as SavedExtensionCreateParams from .saved_extension_list_response import SavedExtensionListResponse as SavedExtensionListResponse @@ -69,8 +71,6 @@ from .video_transformation_ready_event import VideoTransformationReadyEvent as VideoTransformationReadyEvent from .custom_metadata_field_list_params import CustomMetadataFieldListParams as CustomMetadataFieldListParams from .upload_post_transform_error_event import UploadPostTransformErrorEvent as UploadPostTransformErrorEvent -from .file_version_created_webhook_event import FileVersionCreatedWebhookEvent as FileVersionCreatedWebhookEvent -from .file_version_deleted_webhook_event import FileVersionDeletedWebhookEvent as FileVersionDeletedWebhookEvent from .upload_pre_transform_success_event import UploadPreTransformSuccessEvent as UploadPreTransformSuccessEvent from .custom_metadata_field_create_params import CustomMetadataFieldCreateParams as CustomMetadataFieldCreateParams from .custom_metadata_field_list_response import CustomMetadataFieldListResponse as CustomMetadataFieldListResponse diff --git a/src/imagekitio/types/file_created_webhook_event.py b/src/imagekitio/types/file_create_event.py similarity index 85% rename from src/imagekitio/types/file_created_webhook_event.py rename to src/imagekitio/types/file_create_event.py index 3500941..f58bd13 100644 --- a/src/imagekitio/types/file_created_webhook_event.py +++ b/src/imagekitio/types/file_create_event.py @@ -6,10 +6,10 @@ from .file import File from .base_webhook_event import BaseWebhookEvent -__all__ = ["FileCreatedWebhookEvent"] +__all__ = ["FileCreateEvent"] -class FileCreatedWebhookEvent(BaseWebhookEvent): +class FileCreateEvent(BaseWebhookEvent): """Triggered when a file is created.""" created_at: datetime diff --git a/src/imagekitio/types/file_created_webhook_event1.py b/src/imagekitio/types/file_created_webhook_event1.py deleted file mode 100644 index 3500941..0000000 --- a/src/imagekitio/types/file_created_webhook_event1.py +++ /dev/null @@ -1,22 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from datetime import datetime -from typing_extensions import Literal - -from .file import File -from .base_webhook_event import BaseWebhookEvent - -__all__ = ["FileCreatedWebhookEvent"] - - -class FileCreatedWebhookEvent(BaseWebhookEvent): - """Triggered when a file is created.""" - - created_at: datetime - """Timestamp of when the event occurred in ISO8601 format.""" - - data: File - """Object containing details of a file or file version.""" - - type: Literal["file.created"] # type: ignore - """Type of the webhook event.""" diff --git a/src/imagekitio/types/file_deleted_webhook_event.py b/src/imagekitio/types/file_delete_event.py similarity index 74% rename from src/imagekitio/types/file_deleted_webhook_event.py rename to src/imagekitio/types/file_delete_event.py index 4ec9bbe..ca50046 100644 --- a/src/imagekitio/types/file_deleted_webhook_event.py +++ b/src/imagekitio/types/file_delete_event.py @@ -8,21 +8,21 @@ from .._models import BaseModel from .base_webhook_event import BaseWebhookEvent -__all__ = ["FileDeletedWebhookEvent", "FileDeletedWebhookEventData"] +__all__ = ["FileDeleteEvent", "FileDeleteEventData"] -class FileDeletedWebhookEventData(BaseModel): +class FileDeleteEventData(BaseModel): file_id: str = FieldInfo(alias="fileId") """The unique `fileId` of the deleted file.""" -class FileDeletedWebhookEvent(BaseWebhookEvent): +class FileDeleteEvent(BaseWebhookEvent): """Triggered when a file is deleted.""" created_at: datetime """Timestamp of when the event occurred in ISO8601 format.""" - data: FileDeletedWebhookEventData + data: FileDeleteEventData type: Literal["file.deleted"] # type: ignore """Type of the webhook event.""" diff --git a/src/imagekitio/types/file_deleted_webhook_event1.py b/src/imagekitio/types/file_deleted_webhook_event1.py deleted file mode 100644 index 4ec9bbe..0000000 --- a/src/imagekitio/types/file_deleted_webhook_event1.py +++ /dev/null @@ -1,28 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from datetime import datetime -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel -from .base_webhook_event import BaseWebhookEvent - -__all__ = ["FileDeletedWebhookEvent", "FileDeletedWebhookEventData"] - - -class FileDeletedWebhookEventData(BaseModel): - file_id: str = FieldInfo(alias="fileId") - """The unique `fileId` of the deleted file.""" - - -class FileDeletedWebhookEvent(BaseWebhookEvent): - """Triggered when a file is deleted.""" - - created_at: datetime - """Timestamp of when the event occurred in ISO8601 format.""" - - data: FileDeletedWebhookEventData - - type: Literal["file.deleted"] # type: ignore - """Type of the webhook event.""" diff --git a/src/imagekitio/types/file_updated_webhook_event.py b/src/imagekitio/types/file_update_event.py similarity index 85% rename from src/imagekitio/types/file_updated_webhook_event.py rename to src/imagekitio/types/file_update_event.py index 397742b..5bea418 100644 --- a/src/imagekitio/types/file_updated_webhook_event.py +++ b/src/imagekitio/types/file_update_event.py @@ -6,10 +6,10 @@ from .file import File from .base_webhook_event import BaseWebhookEvent -__all__ = ["FileUpdatedWebhookEvent"] +__all__ = ["FileUpdateEvent"] -class FileUpdatedWebhookEvent(BaseWebhookEvent): +class FileUpdateEvent(BaseWebhookEvent): """Triggered when a file is updated.""" created_at: datetime diff --git a/src/imagekitio/types/file_updated_webhook_event1.py b/src/imagekitio/types/file_updated_webhook_event1.py deleted file mode 100644 index 397742b..0000000 --- a/src/imagekitio/types/file_updated_webhook_event1.py +++ /dev/null @@ -1,22 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from datetime import datetime -from typing_extensions import Literal - -from .file import File -from .base_webhook_event import BaseWebhookEvent - -__all__ = ["FileUpdatedWebhookEvent"] - - -class FileUpdatedWebhookEvent(BaseWebhookEvent): - """Triggered when a file is updated.""" - - created_at: datetime - """Timestamp of when the event occurred in ISO8601 format.""" - - data: File - """Object containing details of a file or file version.""" - - type: Literal["file.updated"] # type: ignore - """Type of the webhook event.""" diff --git a/src/imagekitio/types/file_version_created_webhook_event.py b/src/imagekitio/types/file_version_create_event.py similarity index 84% rename from src/imagekitio/types/file_version_created_webhook_event.py rename to src/imagekitio/types/file_version_create_event.py index 242a433..1d5425c 100644 --- a/src/imagekitio/types/file_version_created_webhook_event.py +++ b/src/imagekitio/types/file_version_create_event.py @@ -6,10 +6,10 @@ from .file import File from .base_webhook_event import BaseWebhookEvent -__all__ = ["FileVersionCreatedWebhookEvent"] +__all__ = ["FileVersionCreateEvent"] -class FileVersionCreatedWebhookEvent(BaseWebhookEvent): +class FileVersionCreateEvent(BaseWebhookEvent): """Triggered when a file version is created.""" created_at: datetime diff --git a/src/imagekitio/types/file_version_created_webhook_event1.py b/src/imagekitio/types/file_version_created_webhook_event1.py deleted file mode 100644 index 242a433..0000000 --- a/src/imagekitio/types/file_version_created_webhook_event1.py +++ /dev/null @@ -1,22 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from datetime import datetime -from typing_extensions import Literal - -from .file import File -from .base_webhook_event import BaseWebhookEvent - -__all__ = ["FileVersionCreatedWebhookEvent"] - - -class FileVersionCreatedWebhookEvent(BaseWebhookEvent): - """Triggered when a file version is created.""" - - created_at: datetime - """Timestamp of when the event occurred in ISO8601 format.""" - - data: File - """Object containing details of a file or file version.""" - - type: Literal["file-version.created"] # type: ignore - """Type of the webhook event.""" diff --git a/src/imagekitio/types/file_version_deleted_webhook_event.py b/src/imagekitio/types/file_version_delete_event.py similarity index 75% rename from src/imagekitio/types/file_version_deleted_webhook_event.py rename to src/imagekitio/types/file_version_delete_event.py index 653087a..9c01144 100644 --- a/src/imagekitio/types/file_version_deleted_webhook_event.py +++ b/src/imagekitio/types/file_version_delete_event.py @@ -8,10 +8,10 @@ from .._models import BaseModel from .base_webhook_event import BaseWebhookEvent -__all__ = ["FileVersionDeletedWebhookEvent", "FileVersionDeletedWebhookEventData"] +__all__ = ["FileVersionDeleteEvent", "FileVersionDeleteEventData"] -class FileVersionDeletedWebhookEventData(BaseModel): +class FileVersionDeleteEventData(BaseModel): file_id: str = FieldInfo(alias="fileId") """The unique `fileId` of the deleted file.""" @@ -19,13 +19,13 @@ class FileVersionDeletedWebhookEventData(BaseModel): """The unique `versionId` of the deleted file version.""" -class FileVersionDeletedWebhookEvent(BaseWebhookEvent): +class FileVersionDeleteEvent(BaseWebhookEvent): """Triggered when a file version is deleted.""" created_at: datetime """Timestamp of when the event occurred in ISO8601 format.""" - data: FileVersionDeletedWebhookEventData + data: FileVersionDeleteEventData type: Literal["file-version.deleted"] # type: ignore """Type of the webhook event.""" diff --git a/src/imagekitio/types/file_version_deleted_webhook_event1.py b/src/imagekitio/types/file_version_deleted_webhook_event1.py deleted file mode 100644 index 653087a..0000000 --- a/src/imagekitio/types/file_version_deleted_webhook_event1.py +++ /dev/null @@ -1,31 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from datetime import datetime -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel -from .base_webhook_event import BaseWebhookEvent - -__all__ = ["FileVersionDeletedWebhookEvent", "FileVersionDeletedWebhookEventData"] - - -class FileVersionDeletedWebhookEventData(BaseModel): - file_id: str = FieldInfo(alias="fileId") - """The unique `fileId` of the deleted file.""" - - version_id: str = FieldInfo(alias="versionId") - """The unique `versionId` of the deleted file version.""" - - -class FileVersionDeletedWebhookEvent(BaseWebhookEvent): - """Triggered when a file version is deleted.""" - - created_at: datetime - """Timestamp of when the event occurred in ISO8601 format.""" - - data: FileVersionDeletedWebhookEventData - - type: Literal["file-version.deleted"] # type: ignore - """Type of the webhook event.""" diff --git a/src/imagekitio/types/unsafe_unwrap_webhook_event.py b/src/imagekitio/types/unsafe_unwrap_webhook_event.py index 37f0a56..7d10c69 100644 --- a/src/imagekitio/types/unsafe_unwrap_webhook_event.py +++ b/src/imagekitio/types/unsafe_unwrap_webhook_event.py @@ -4,15 +4,15 @@ from typing_extensions import Annotated, TypeAlias from .._utils import PropertyInfo -from .file_created_webhook_event import FileCreatedWebhookEvent -from .file_deleted_webhook_event import FileDeletedWebhookEvent -from .file_updated_webhook_event import FileUpdatedWebhookEvent +from .file_create_event import FileCreateEvent +from .file_delete_event import FileDeleteEvent +from .file_update_event import FileUpdateEvent +from .file_version_create_event import FileVersionCreateEvent +from .file_version_delete_event import FileVersionDeleteEvent from .upload_pre_transform_error_event import UploadPreTransformErrorEvent from .video_transformation_error_event import VideoTransformationErrorEvent from .video_transformation_ready_event import VideoTransformationReadyEvent from .upload_post_transform_error_event import UploadPostTransformErrorEvent -from .file_version_created_webhook_event import FileVersionCreatedWebhookEvent -from .file_version_deleted_webhook_event import FileVersionDeletedWebhookEvent from .upload_pre_transform_success_event import UploadPreTransformSuccessEvent from .upload_post_transform_success_event import UploadPostTransformSuccessEvent from .video_transformation_accepted_event import VideoTransformationAcceptedEvent @@ -28,11 +28,11 @@ UploadPreTransformErrorEvent, UploadPostTransformSuccessEvent, UploadPostTransformErrorEvent, - FileCreatedWebhookEvent, - FileUpdatedWebhookEvent, - FileDeletedWebhookEvent, - FileVersionCreatedWebhookEvent, - FileVersionDeletedWebhookEvent, + FileCreateEvent, + FileUpdateEvent, + FileDeleteEvent, + FileVersionCreateEvent, + FileVersionDeleteEvent, ], PropertyInfo(discriminator="type"), ] diff --git a/src/imagekitio/types/unwrap_webhook_event.py b/src/imagekitio/types/unwrap_webhook_event.py index de16986..8614c55 100644 --- a/src/imagekitio/types/unwrap_webhook_event.py +++ b/src/imagekitio/types/unwrap_webhook_event.py @@ -4,15 +4,15 @@ from typing_extensions import Annotated, TypeAlias from .._utils import PropertyInfo -from .file_created_webhook_event import FileCreatedWebhookEvent -from .file_deleted_webhook_event import FileDeletedWebhookEvent -from .file_updated_webhook_event import FileUpdatedWebhookEvent +from .file_create_event import FileCreateEvent +from .file_delete_event import FileDeleteEvent +from .file_update_event import FileUpdateEvent +from .file_version_create_event import FileVersionCreateEvent +from .file_version_delete_event import FileVersionDeleteEvent from .upload_pre_transform_error_event import UploadPreTransformErrorEvent from .video_transformation_error_event import VideoTransformationErrorEvent from .video_transformation_ready_event import VideoTransformationReadyEvent from .upload_post_transform_error_event import UploadPostTransformErrorEvent -from .file_version_created_webhook_event import FileVersionCreatedWebhookEvent -from .file_version_deleted_webhook_event import FileVersionDeletedWebhookEvent from .upload_pre_transform_success_event import UploadPreTransformSuccessEvent from .upload_post_transform_success_event import UploadPostTransformSuccessEvent from .video_transformation_accepted_event import VideoTransformationAcceptedEvent @@ -28,11 +28,11 @@ UploadPreTransformErrorEvent, UploadPostTransformSuccessEvent, UploadPostTransformErrorEvent, - FileCreatedWebhookEvent, - FileUpdatedWebhookEvent, - FileDeletedWebhookEvent, - FileVersionCreatedWebhookEvent, - FileVersionDeletedWebhookEvent, + FileCreateEvent, + FileUpdateEvent, + FileDeleteEvent, + FileVersionCreateEvent, + FileVersionDeleteEvent, ], PropertyInfo(discriminator="type"), ] From aa0272a8fe212b1a841031d25fddaa49359ec9d9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:55:18 +0000 Subject: [PATCH 8/9] fix: ensure file data are only sent as 1 parameter --- src/imagekitio/_utils/_utils.py | 5 +++-- tests/test_extract_files.py | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/imagekitio/_utils/_utils.py b/src/imagekitio/_utils/_utils.py index eec7f4a..63b8cd6 100644 --- a/src/imagekitio/_utils/_utils.py +++ b/src/imagekitio/_utils/_utils.py @@ -86,8 +86,9 @@ def _extract_items( index += 1 if is_dict(obj): try: - # We are at the last entry in the path so we must remove the field - if (len(path)) == index: + # Remove the field if there are no more dict keys in the path, + # only "" traversal markers or end. + if all(p == "" for p in path[index:]): item = obj.pop(key) else: item = obj[key] diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py index 396cbd0..9b9780e 100644 --- a/tests/test_extract_files.py +++ b/tests/test_extract_files.py @@ -35,6 +35,15 @@ def test_multiple_files() -> None: assert query == {"documents": [{}, {}]} +def test_top_level_file_array() -> None: + query = {"files": [b"file one", b"file two"], "title": "hello"} + assert extract_files(query, paths=[["files", ""]]) == [ + ("files[]", b"file one"), + ("files[]", b"file two"), + ] + assert query == {"title": "hello"} + + @pytest.mark.parametrize( "query,paths,expected", [ From 420fca165a2dc3113eab277951a1e705ec2ee15f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:55:40 +0000 Subject: [PATCH 9/9] release: 5.4.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 19 +++++++++++++++++++ pyproject.toml | 2 +- src/imagekitio/_version.py | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 4101074..5f7bf25 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "5.3.0" + ".": "5.4.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 01525ca..64facc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## 5.4.0 (2026-04-10) + +Full Changelog: [v5.3.0...v5.4.0](https://github.com/imagekit-developer/imagekit-python/compare/v5.3.0...v5.4.0) + +### Features + +* **api:** dam related webhook events ([8803680](https://github.com/imagekit-developer/imagekit-python/commit/8803680ae4bb3ea801d71520cc1354b7a1558bc6)) +* **api:** fix spec indentation ([1a2417d](https://github.com/imagekit-developer/imagekit-python/commit/1a2417d4336d1b9403eb1bc2b65187209fe833c7)) +* **api:** indentation fix ([6ad7341](https://github.com/imagekit-developer/imagekit-python/commit/6ad7341af30e43252519a3c44826be408323cbbe)) +* **api:** merge with main to bring back missing parameters ([a07e952](https://github.com/imagekit-developer/imagekit-python/commit/a07e95275e50dcd975f3ec816420eee7645ce223)) +* **api:** update webhook event names and remove DAM prefix ([bf9e082](https://github.com/imagekit-developer/imagekit-python/commit/bf9e082da50cea2f983b5bd88caca825e5039ec5)) + + +### Bug Fixes + +* **api:** rename DamFile events to File for consistency ([16b113f](https://github.com/imagekit-developer/imagekit-python/commit/16b113f1e6f42b4ac1af43c4cf0567cae55f6ecf)) +* **client:** preserve hardcoded query params when merging with user params ([cbdc71f](https://github.com/imagekit-developer/imagekit-python/commit/cbdc71fee37ce26c0a05cabc55cb03b46c29b216)) +* ensure file data are only sent as 1 parameter ([aa0272a](https://github.com/imagekit-developer/imagekit-python/commit/aa0272a8fe212b1a841031d25fddaa49359ec9d9)) + ## 5.3.0 (2026-04-06) Full Changelog: [v5.2.0...v5.3.0](https://github.com/imagekit-developer/imagekit-python/compare/v5.2.0...v5.3.0) diff --git a/pyproject.toml b/pyproject.toml index f04a830..09cbd54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "imagekitio" -version = "5.3.0" +version = "5.4.0" description = "The official Python library for the ImageKit API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/imagekitio/_version.py b/src/imagekitio/_version.py index 7e78486..3d6ca86 100644 --- a/src/imagekitio/_version.py +++ b/src/imagekitio/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "imagekitio" -__version__ = "5.3.0" # x-release-please-version +__version__ = "5.4.0" # x-release-please-version