Skip to content

Commit 26db0c7

Browse files
authored
Merge pull request #82 from imagekit-developer/release-please--branches--master--changes--next
release: 5.5.0
2 parents baf3387 + 52cb8a3 commit 26db0c7

27 files changed

Lines changed: 481 additions & 131 deletions

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
runs-on: ${{ github.repository == 'stainless-sdks/imagekit-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
2222
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
2323
steps:
24-
- uses: actions/checkout@v6
24+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2525

2626
- name: Install Rye
2727
run: |
@@ -46,7 +46,7 @@ jobs:
4646
id-token: write
4747
runs-on: ${{ github.repository == 'stainless-sdks/imagekit-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
4848
steps:
49-
- uses: actions/checkout@v6
49+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
5050

5151
- name: Install Rye
5252
run: |
@@ -67,7 +67,7 @@ jobs:
6767
github.repository == 'stainless-sdks/imagekit-python' &&
6868
!startsWith(github.ref, 'refs/heads/stl/')
6969
id: github-oidc
70-
uses: actions/github-script@v8
70+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
7171
with:
7272
script: core.setOutput('github_token', await core.getIDToken());
7373

@@ -87,7 +87,7 @@ jobs:
8787
runs-on: ${{ github.repository == 'stainless-sdks/imagekit-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
8888
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
8989
steps:
90-
- uses: actions/checkout@v6
90+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
9191

9292
- name: Install Rye
9393
run: |

.github/workflows/publish-pypi.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
runs-on: ubuntu-latest
1515

1616
steps:
17-
- uses: actions/checkout@v6
17+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
1818

1919
- name: Install Rye
2020
run: |

.github/workflows/release-doctor.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
if: github.repository == 'imagekit-developer/imagekit-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
1313

1414
steps:
15-
- uses: actions/checkout@v6
15+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
1616

1717
- name: Check release environment
1818
run: |

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "5.4.0"
2+
".": "5.5.0"
33
}

.stats.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 48
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-70c42eda2bee929830b2537f758400a58dded1f1ef5686a286e2469c35a041a0.yml
3-
openapi_spec_hash: cdaeed824e91657b45092765cf55eb42
4-
config_hash: e3c2679d25f6235381dfb11962fbf3d9
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc/imagekit-ad6dd3b4acf289708568a12574b997503059a47c4a4ca5ffefe64f40f3d3dbf3.yml
3+
openapi_spec_hash: 7c103e2dff0edcbeea82057e62f58d4d
4+
config_hash: 94f48fd13b7d41b8b6a203a3a8cee9ed

CHANGELOG.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,36 @@
11
# Changelog
22

3+
## 5.5.0 (2026-05-13)
4+
5+
Full Changelog: [v5.4.0...v5.5.0](https://github.com/imagekit-developer/imagekit-python/compare/v5.4.0...v5.5.0)
6+
7+
### Features
8+
9+
* **api:** add no-enlarge crop modes and colorize transformation ([c4ddf30](https://github.com/imagekit-developer/imagekit-python/commit/c4ddf30c84d5d10a483ab5b2563510f049e414b2))
10+
* **api:** manual updates ([965e263](https://github.com/imagekit-developer/imagekit-python/commit/965e26380c05a76c356fc994e0a92dc8efe3ac4e))
11+
* **helper:** add colorize transformation to supported transforms ([8a96c55](https://github.com/imagekit-developer/imagekit-python/commit/8a96c55d03a9f1448954dd56510781b10b858436))
12+
* **internal/types:** support eagerly validating pydantic iterators ([35bcec1](https://github.com/imagekit-developer/imagekit-python/commit/35bcec134edcbbf356f5f93d5a1e719f69b458a3))
13+
* support setting headers via env ([39cbf90](https://github.com/imagekit-developer/imagekit-python/commit/39cbf90f16813f2ee8f477554ece14f804cc765d))
14+
* **tests:** add colorize transformation to advanced URL generation test ([efa4d19](https://github.com/imagekit-developer/imagekit-python/commit/efa4d197ca7d8a3cba88243d8c187400f17b2dea))
15+
16+
17+
### Bug Fixes
18+
19+
* **client:** add missing f-string prefix in file type error message ([b3926a7](https://github.com/imagekit-developer/imagekit-python/commit/b3926a7b73d379cf56ce3985bc602254fda83f40))
20+
* use correct field name format for multipart file arrays ([c89d2b3](https://github.com/imagekit-developer/imagekit-python/commit/c89d2b36f2049df445ac5f365ed8dac2544d116b))
21+
22+
23+
### Performance Improvements
24+
25+
* **client:** optimize file structure copying in multipart requests ([b22ab86](https://github.com/imagekit-developer/imagekit-python/commit/b22ab86e45de2bbc479942eaa27dd0c9b89cbc91))
26+
27+
28+
### Chores
29+
30+
* configure new SDK language ([2b40f08](https://github.com/imagekit-developer/imagekit-python/commit/2b40f08e2757811dd14e29aed78f4f928fd97111))
31+
* **internal:** more robust bootstrap script ([e5703df](https://github.com/imagekit-developer/imagekit-python/commit/e5703dfdb61f9842a508947555544ad9910a225d))
32+
* **internal:** reformat pyproject.toml ([15a2ce1](https://github.com/imagekit-developer/imagekit-python/commit/15a2ce1f0293a0ab4d96792379e127f68f5cc64d))
33+
334
## 5.4.0 (2026-04-13)
435

536
Full Changelog: [v5.3.0...v5.4.0](https://github.com/imagekit-developer/imagekit-python/compare/v5.3.0...v5.4.0)

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "imagekitio"
3-
version = "5.4.0"
3+
version = "5.5.0"
44
description = "The official Python library for the ImageKit API"
55
dynamic = ["readme"]
66
license = "Apache-2.0"
@@ -169,7 +169,7 @@ show_error_codes = true
169169
#
170170
# We also exclude our `tests` as mypy doesn't always infer
171171
# types correctly and Pyright will still catch any type errors.
172-
exclude = ['src/imagekitio/_files.py', '_dev/.*.py', 'tests/.*']
172+
exclude = ["src/imagekitio/_files.py", "_dev/.*.py", "tests/.*"]
173173

174174
strict_equality = true
175175
implicit_reexport = true

scripts/bootstrap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ set -e
44

55
cd "$(dirname "$0")/.."
66

7-
if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then
7+
if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then
88
brew bundle check >/dev/null 2>&1 || {
99
echo -n "==> Install Homebrew dependencies? (y/N): "
1010
read -r response

src/imagekitio/_client.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@
2121
RequestOptions,
2222
not_given,
2323
)
24-
from ._utils import is_given, get_async_library
24+
from ._utils import (
25+
is_given,
26+
is_mapping_t,
27+
get_async_library,
28+
)
2529
from ._compat import cached_property
2630
from ._version import __version__
2731
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
@@ -128,6 +132,15 @@ def __init__(
128132
if base_url is None:
129133
base_url = f"https://api.imagekit.io"
130134

135+
custom_headers_env = os.environ.get("IMAGE_KIT_CUSTOM_HEADERS")
136+
if custom_headers_env is not None:
137+
parsed: dict[str, str] = {}
138+
for line in custom_headers_env.split("\n"):
139+
colon = line.find(":")
140+
if colon >= 0:
141+
parsed[line[:colon].strip()] = line[colon + 1 :].strip()
142+
default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}
143+
131144
super().__init__(
132145
version=__version__,
133146
base_url=base_url,
@@ -396,6 +409,15 @@ def __init__(
396409
if base_url is None:
397410
base_url = f"https://api.imagekit.io"
398411

412+
custom_headers_env = os.environ.get("IMAGE_KIT_CUSTOM_HEADERS")
413+
if custom_headers_env is not None:
414+
parsed: dict[str, str] = {}
415+
for line in custom_headers_env.split("\n"):
416+
colon = line.find(":")
417+
if colon >= 0:
418+
parsed[line[:colon].strip()] = line[colon + 1 :].strip()
419+
default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}
420+
399421
super().__init__(
400422
version=__version__,
401423
base_url=base_url,

src/imagekitio/_files.py

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import io
44
import os
55
import pathlib
6-
from typing import overload
7-
from typing_extensions import TypeGuard
6+
from typing import Sequence, cast, overload
7+
from typing_extensions import TypeVar, TypeGuard
88

99
import anyio
1010

@@ -17,7 +17,9 @@
1717
HttpxFileContent,
1818
HttpxRequestFiles,
1919
)
20-
from ._utils import is_tuple_t, is_mapping_t, is_sequence_t
20+
from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t
21+
22+
_T = TypeVar("_T")
2123

2224

2325
def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]:
@@ -97,7 +99,7 @@ async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles
9799
elif is_sequence_t(files):
98100
files = [(key, await _async_transform_file(file)) for key, file in files]
99101
else:
100-
raise TypeError("Unexpected file type input {type(files)}, expected mapping or sequence")
102+
raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence")
101103

102104
return files
103105

@@ -121,3 +123,51 @@ async def async_read_file_content(file: FileContent) -> HttpxFileContent:
121123
return await anyio.Path(file).read_bytes()
122124

123125
return file
126+
127+
128+
def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T:
129+
"""Copy only the containers along the given paths.
130+
131+
Used to guard against mutation by extract_files without copying the entire structure.
132+
Only dicts and lists that lie on a path are copied; everything else
133+
is returned by reference.
134+
135+
For example, given paths=[["foo", "files", "file"]] and the structure:
136+
{
137+
"foo": {
138+
"bar": {"baz": {}},
139+
"files": {"file": <content>}
140+
}
141+
}
142+
The root dict, "foo", and "files" are copied (they lie on the path).
143+
"bar" and "baz" are returned by reference (off the path).
144+
"""
145+
return _deepcopy_with_paths(item, paths, 0)
146+
147+
148+
def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T:
149+
if not paths:
150+
return item
151+
if is_mapping(item):
152+
key_to_paths: dict[str, list[Sequence[str]]] = {}
153+
for path in paths:
154+
if index < len(path):
155+
key_to_paths.setdefault(path[index], []).append(path)
156+
157+
# if no path continues through this mapping, it won't be mutated and copying it is redundant
158+
if not key_to_paths:
159+
return item
160+
161+
result = dict(item)
162+
for key, subpaths in key_to_paths.items():
163+
if key in result:
164+
result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1)
165+
return cast(_T, result)
166+
if is_list(item):
167+
array_paths = [path for path in paths if index < len(path) and path[index] == "<array>"]
168+
169+
# if no path expects a list here, nothing will be mutated inside it - return by reference
170+
if not array_paths:
171+
return cast(_T, item)
172+
return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item])
173+
return item

0 commit comments

Comments
 (0)