Skip to content

Commit c595fa1

Browse files
authored
Merge pull request #14 from aymara-ai/release-please--branches--main--changes--next
release: 1.0.0-alpha.13
2 parents a9f3161 + f5c8fe9 commit c595fa1

File tree

9 files changed

+338
-119
lines changed

9 files changed

+338
-119
lines changed

.release-please-manifest.json

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

CHANGELOG.md

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

3+
## 1.0.0-alpha.13 (2025-05-02)
4+
5+
Full Changelog: [v1.0.0-alpha.12...v1.0.0-alpha.13](https://github.com/aymara-ai/aymara-sdk-python/compare/v1.0.0-alpha.12...v1.0.0-alpha.13)
6+
7+
### Features
8+
9+
* file upload utils ([2a728da](https://github.com/aymara-ai/aymara-sdk-python/commit/2a728dadbd363952487687a5bbfa21935877fe36))
10+
11+
12+
### Bug Fixes
13+
14+
* image generation ([9a78e78](https://github.com/aymara-ai/aymara-sdk-python/commit/9a78e78fddcd44052e8e3c61d4c68baedda68387))
15+
16+
17+
### Chores
18+
19+
* lint ([53cbfbd](https://github.com/aymara-ai/aymara-sdk-python/commit/53cbfbd1702b4f04a1a778993ec275a28168f98c))
20+
* lint ([2a0caac](https://github.com/aymara-ai/aymara-sdk-python/commit/2a0caac33ecfaf4f6ef64194bc607fc0ec5b8a91))
21+
322
## 1.0.0-alpha.12 (2025-05-01)
423

524
Full Changelog: [v1.0.0-alpha.11...v1.0.0-alpha.12](https://github.com/aymara-ai/aymara-sdk-python/compare/v1.0.0-alpha.11...v1.0.0-alpha.12)

examples/image_safety_eval.ipynb

Lines changed: 160 additions & 38 deletions
Large diffs are not rendered by default.

examples/image_safety_eval_runner.ipynb

Lines changed: 20 additions & 25 deletions
Large diffs are not rendered by default.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "aymara-ai-sdk"
3-
version = "1.0.0-alpha.12"
3+
version = "1.0.0-alpha.13"
44
description = "The official Python library for the aymara-ai API"
55
dynamic = ["readme"]
66
license = "Apache-2.0"

src/aymara_ai/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

33
__title__ = "aymara_ai"
4-
__version__ = "1.0.0-alpha.12" # x-release-please-version
4+
__version__ = "1.0.0-alpha.13" # x-release-please-version

src/aymara_ai/lib/images_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def trim_caption(caption):
126126
row += 1
127127

128128
# Image row
129-
images = [a["local_file_path"] for a in responses[:n_images_per_eval] if a.ai_refused is False]
129+
images = [a["local_file_path"] for a in responses[:n_images_per_eval] if a.get("ai_refused", False) is False]
130130
if eval_runs is None:
131131
captions = [
132132
next(

src/aymara_ai/lib/runner.py

Lines changed: 18 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
from pathlib import Path
88

99
import httpx
10-
import aiofiles
1110

1211
from aymara_ai import AymaraAI, AsyncAymaraAI
12+
from aymara_ai.lib.uploads import upload_file, upload_file_async
1313
from aymara_ai.lib.async_utils import wait_until_complete, async_wait_until_complete
1414
from aymara_ai.types.eval_prompt import EvalPrompt
1515
from aymara_ai.types.shared_params import FileReference
@@ -57,27 +57,6 @@ def _default_response_adapter(model_output: Union[str, Path], prompt: EvalPrompt
5757
else:
5858
raise ValueError("Unsupported model output type for response adapter.")
5959

60-
def upload_file_content(
61-
self, *, client: httpx.Client, file_name: str, file_content: Union[Path, bytes]
62-
) -> FileReference:
63-
"""
64-
Helper to upload file content (from Path or bytes) and return (FileReference).
65-
"""
66-
upload_resp = self.client.files.create(files=[{"local_file_path": file_name}])
67-
file_info = upload_resp.files[0]
68-
if not file_info.file_url:
69-
raise RuntimeError("No presigned file_url returned for upload.")
70-
if isinstance(file_content, Path):
71-
with open(str(file_content), "rb") as f:
72-
put_resp = client.put(file_info.file_url, content=f)
73-
put_resp.raise_for_status()
74-
elif isinstance(file_content, bytes): # type: ignore
75-
put_resp = client.put(file_info.file_url, content=file_content)
76-
put_resp.raise_for_status()
77-
else:
78-
raise ValueError("Unsupported file_content type for upload.")
79-
return FileReference(remote_file_path=file_info.remote_file_path)
80-
8160
def run_eval(
8261
self,
8362
eval_params: Dict[str, Any],
@@ -114,17 +93,23 @@ def run_eval(
11493
continue
11594

11695
file_content = model_output
117-
file_name = "model_output.png"
96+
file_name = None
11897
if isinstance(model_output, Path): # type: ignore
11998
file_content = model_output
12099
file_name = str(model_output)
121-
file_ref = self.upload_file_content(client=client, file_content=file_content, file_name=file_name)
100+
file_ref = upload_file(
101+
client=self.client,
102+
http_client=client,
103+
file_name=file_name,
104+
file_content=file_content,
105+
content_type=None,
106+
)
122107
response = EvalResponseParam(
123108
content=file_ref,
124109
prompt_uuid=prompt.prompt_uuid,
125110
content_type="image",
126111
)
127-
response["local_file_path"] = file_name # type: ignore
112+
response["local_file_path"] = file_name if file_name is not None else "file.png" # type: ignore
128113
responses.append(response)
129114

130115
# 5. Create eval run
@@ -175,29 +160,6 @@ def _default_response_adapter(model_output: Union[str, Path], prompt: EvalPrompt
175160
else:
176161
raise ValueError("Unsupported model output type for response adapter.")
177162

178-
async def upload_file_content_async(
179-
self, *, client: httpx.AsyncClient, file_name: str, file_content: Union[Path, bytes]
180-
) -> FileReference:
181-
"""
182-
Async helper to upload file content (from Path or bytes) and return (FileReference).
183-
"""
184-
upload_resp = await self.client.files.create(files=[{"local_file_path": file_name}])
185-
file_info = upload_resp.files[0]
186-
if not file_info.file_url:
187-
raise RuntimeError("No presigned file_url returned for upload.")
188-
if isinstance(file_content, Path):
189-
async with aiofiles.open(str(file_content), mode="rb") as f:
190-
put_resp = await client.put(file_info.file_url, content=f)
191-
if put_resp.status_code != 200:
192-
raise RuntimeError(f"Failed to upload file: {put_resp.status_code}")
193-
elif isinstance(file_content, bytes): # type: ignore
194-
put_resp = await client.put(file_info.file_url, content=file_content)
195-
if put_resp.status_code != 200:
196-
raise RuntimeError(f"Failed to upload file: {put_resp.status_code}")
197-
else:
198-
raise ValueError("Unsupported file_content type for upload.")
199-
return FileReference(remote_file_path=file_info.remote_file_path)
200-
201163
async def run_eval(
202164
self,
203165
eval_params: Dict[str, Any],
@@ -237,19 +199,23 @@ async def run_eval(
237199
continue
238200

239201
file_content = model_output
240-
file_name = "model_output.png"
202+
file_name = None
241203
if isinstance(model_output, Path): # type: ignore
242204
file_content = model_output
243205
file_name = str(model_output)
244-
file_ref = await self.upload_file_content_async(
245-
client=client, file_name=file_name, file_content=file_content
206+
file_ref = await upload_file_async(
207+
client=self.client,
208+
http_client=client,
209+
file_name=file_name,
210+
file_content=file_content,
211+
content_type=None,
246212
)
247213
response = EvalResponseParam(
248214
content=file_ref,
249215
prompt_uuid=prompt.prompt_uuid,
250216
content_type="image",
251217
)
252-
response["local_file_path"] = file_name # type: ignore
218+
response["local_file_path"] = file_name if file_name is not None else "file.png" # type: ignore
253219
responses.append(response)
254220

255221
# 5. Create eval run

src/aymara_ai/lib/uploads.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import mimetypes
2+
from typing import Union, Optional
3+
from pathlib import Path
4+
5+
import httpx
6+
import aiofiles
7+
8+
from aymara_ai import AymaraAI, AsyncAymaraAI
9+
from aymara_ai.types.shared_params import FileReference
10+
11+
12+
def _default_file_name(content_type: Optional[str]) -> str:
13+
if content_type:
14+
ext = mimetypes.guess_extension(content_type)
15+
if ext:
16+
return f"file{ext}"
17+
# fallback if extension can't be guessed
18+
return "file.bin"
19+
return "file.png"
20+
21+
22+
def upload_file(
23+
client: Optional[AymaraAI] = None,
24+
http_client: Optional[httpx.Client] = None,
25+
file_name: Optional[str] = None,
26+
file_content: Union[Path, bytes, None] = None,
27+
content_type: Optional[str] = None,
28+
) -> FileReference:
29+
"""
30+
Helper to upload file content (from Path or bytes) and return (FileReference).
31+
Both client and http_client are optional and will be constructed if not provided.
32+
file_name is optional and will be defaulted based on content_type or to 'file.png'.
33+
content_type is optional and will be guessed from file_name if not provided.
34+
"""
35+
if client is None:
36+
client = AymaraAI()
37+
close_http_client = False
38+
if http_client is None:
39+
http_client = httpx.Client()
40+
close_http_client = True
41+
42+
try:
43+
# Determine file_name
44+
if file_name is None:
45+
file_name = _default_file_name(content_type)
46+
# Determine content_type
47+
mime_type = content_type or mimetypes.guess_type(file_name)[0] or "application/octet-stream"
48+
headers = {"Content-Type": mime_type}
49+
50+
upload_resp = client.files.create(files=[{"local_file_path": file_name}])
51+
file_info = upload_resp.files[0]
52+
if not file_info.file_url:
53+
raise RuntimeError("No presigned file_url returned for upload.")
54+
55+
if isinstance(file_content, Path):
56+
with open(str(file_content), "rb") as f:
57+
put_resp = http_client.put(file_info.file_url, content=f, headers=headers)
58+
put_resp.raise_for_status()
59+
elif isinstance(file_content, bytes):
60+
put_resp = http_client.put(file_info.file_url, content=file_content, headers=headers)
61+
put_resp.raise_for_status()
62+
else:
63+
raise ValueError("Unsupported file_content type for upload.")
64+
return FileReference(remote_file_path=file_info.remote_file_path)
65+
finally:
66+
if close_http_client:
67+
http_client.close()
68+
69+
70+
async def upload_file_async(
71+
client: Optional[AsyncAymaraAI] = None,
72+
http_client: Optional[httpx.AsyncClient] = None,
73+
file_name: Optional[str] = None,
74+
file_content: Union[Path, bytes, None] = None,
75+
content_type: Optional[str] = None,
76+
) -> FileReference:
77+
"""
78+
Async helper to upload file content (from Path or bytes) and return (FileReference).
79+
Both client and http_client are optional and will be constructed if not provided.
80+
file_name is optional and will be defaulted based on content_type or to 'file.png'.
81+
content_type is optional and will be guessed from file_name if not provided.
82+
"""
83+
if client is None:
84+
client = AsyncAymaraAI()
85+
close_http_client = False
86+
if http_client is None:
87+
http_client = httpx.AsyncClient()
88+
close_http_client = True
89+
90+
try:
91+
# Determine file_name
92+
if file_name is None:
93+
file_name = _default_file_name(content_type)
94+
# Determine content_type
95+
mime_type = content_type or mimetypes.guess_type(file_name)[0] or "application/octet-stream"
96+
headers = {"Content-Type": mime_type}
97+
98+
upload_resp = await client.files.create(files=[{"local_file_path": file_name}])
99+
file_info = upload_resp.files[0]
100+
if not file_info.file_url:
101+
raise RuntimeError("No presigned file_url returned for upload.")
102+
103+
if isinstance(file_content, Path):
104+
async with aiofiles.open(str(file_content), mode="rb") as f:
105+
put_resp = await http_client.put(file_info.file_url, content=f, headers=headers)
106+
if put_resp.status_code != 200:
107+
raise RuntimeError(f"Failed to upload file: {put_resp.status_code}")
108+
elif isinstance(file_content, bytes):
109+
put_resp = await http_client.put(file_info.file_url, content=file_content, headers=headers)
110+
if put_resp.status_code != 200:
111+
raise RuntimeError(f"Failed to upload file: {put_resp.status_code}")
112+
else:
113+
raise ValueError("Unsupported file_content type for upload.")
114+
return FileReference(remote_file_path=file_info.remote_file_path)
115+
finally:
116+
if close_http_client:
117+
await http_client.aclose()

0 commit comments

Comments
 (0)