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/.stats.yml b/.stats.yml index 0e8db84..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-63aff1629530786015da3c86131afa8a9b60545d488884b77641f1d4b89c6e9d.yml -openapi_spec_hash: 586d357bd7e5217d240a99e0d83c6d1f -config_hash: 47cb702ee2cb52c58d803ae39ade9b44 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-d73a37dc3426586109bd153f02c6a605036b6a7396bba5173d013468c5291ce6.yml +openapi_spec_hash: c193c6e557ff477481ec8d5ac8a0c96e +config_hash: 32b155378f65c234d3abeb18519fb3cd 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/api.md b/api.md index 2a17f3a..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, 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/_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/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/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 diff --git a/src/imagekitio/types/__init__.py b/src/imagekitio/types/__init__.py index 180fe49..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,6 +59,8 @@ 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 .unsafe_unwrap_webhook_event import UnsafeUnwrapWebhookEvent as UnsafeUnwrapWebhookEvent from .saved_extension_create_params import SavedExtensionCreateParams as SavedExtensionCreateParams diff --git a/src/imagekitio/types/file_create_event.py b/src/imagekitio/types/file_create_event.py new file mode 100644 index 0000000..f58bd13 --- /dev/null +++ b/src/imagekitio/types/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__ = ["FileCreateEvent"] + + +class FileCreateEvent(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_delete_event.py b/src/imagekitio/types/file_delete_event.py new file mode 100644 index 0000000..ca50046 --- /dev/null +++ b/src/imagekitio/types/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__ = ["FileDeleteEvent", "FileDeleteEventData"] + + +class FileDeleteEventData(BaseModel): + file_id: str = FieldInfo(alias="fileId") + """The unique `fileId` of the deleted file.""" + + +class FileDeleteEvent(BaseWebhookEvent): + """Triggered when a file is deleted.""" + + created_at: datetime + """Timestamp of when the event occurred in ISO8601 format.""" + + data: FileDeleteEventData + + type: Literal["file.deleted"] # type: ignore + """Type of the webhook event.""" diff --git a/src/imagekitio/types/file_update_event.py b/src/imagekitio/types/file_update_event.py new file mode 100644 index 0000000..5bea418 --- /dev/null +++ b/src/imagekitio/types/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__ = ["FileUpdateEvent"] + + +class FileUpdateEvent(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_create_event.py b/src/imagekitio/types/file_version_create_event.py new file mode 100644 index 0000000..1d5425c --- /dev/null +++ b/src/imagekitio/types/file_version_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__ = ["FileVersionCreateEvent"] + + +class FileVersionCreateEvent(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_delete_event.py b/src/imagekitio/types/file_version_delete_event.py new file mode 100644 index 0000000..9c01144 --- /dev/null +++ b/src/imagekitio/types/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__ = ["FileVersionDeleteEvent", "FileVersionDeleteEventData"] + + +class FileVersionDeleteEventData(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 FileVersionDeleteEvent(BaseWebhookEvent): + """Triggered when a file version is deleted.""" + + created_at: datetime + """Timestamp of when the event occurred in ISO8601 format.""" + + data: FileVersionDeleteEventData + + 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 9ed05b3..7d10c69 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 .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 @@ -23,6 +28,11 @@ UploadPreTransformErrorEvent, UploadPostTransformSuccessEvent, UploadPostTransformErrorEvent, + 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 e67355f..8614c55 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 .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 @@ -23,6 +28,11 @@ UploadPreTransformErrorEvent, UploadPostTransformSuccessEvent, UploadPostTransformErrorEvent, + FileCreateEvent, + FileUpdateEvent, + FileDeleteEvent, + FileVersionCreateEvent, + FileVersionDeleteEvent, ], PropertyInfo(discriminator="type"), ] 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( 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", [