Skip to content

Commit 366af16

Browse files
authored
feat: add UrlArtifact, ImageUrlArtifact (#1913)
Closes #1359
1 parent c65fe69 commit 366af16

File tree

10 files changed

+116
-4
lines changed

10 files changed

+116
-4
lines changed

docs/griptape-framework/data/artifacts.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ When `BlobArtifact`s are returned from Tools, they will be stored in [Task Memor
2323

2424
[ImageArtifact](../../reference/griptape/artifacts/image_artifact.md)s store image data. This includes binary image data along with metadata such as MIME type and dimensions. They are a subclass of [BlobArtifacts](#blob).
2525

26+
### Url
27+
28+
[UrlArtifact](../../reference/griptape/artifacts/url_artifact.md)s store URLs pointing to resources.
29+
30+
### Image Url
31+
32+
[ImageUrlArtifact](../../reference/griptape/artifacts/image_url_artifact.md)s store URLs pointing to images. They are a subclass of [UrlArtifact](#url).
33+
2634
### Audio
2735

2836
[AudioArtifact](../../reference/griptape/artifacts/audio_artifact.md)s store audio content. This includes binary audio data and metadata such as format, and duration. They are a subclass of [BlobArtifacts](#blob).

griptape/artifacts/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
from .text_artifact import TextArtifact
55
from .json_artifact import JsonArtifact
66
from .blob_artifact import BlobArtifact
7+
from .url_artifact import UrlArtifact
78
from .boolean_artifact import BooleanArtifact
89
from .list_artifact import ListArtifact
910
from .image_artifact import ImageArtifact
11+
from .image_url_artifact import ImageUrlArtifact
1012
from .audio_artifact import AudioArtifact
1113
from .action_artifact import ActionArtifact
1214
from .generic_artifact import GenericArtifact
@@ -18,10 +20,12 @@
1820
"AudioArtifact",
1921
"BaseArtifact",
2022
"BlobArtifact",
23+
"UrlArtifact",
2124
"BooleanArtifact",
2225
"ErrorArtifact",
2326
"GenericArtifact",
2427
"ImageArtifact",
28+
"ImageUrlArtifact",
2529
"InfoArtifact",
2630
"JsonArtifact",
2731
"ListArtifact",

griptape/artifacts/audio_artifact.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def __attrs_post_init__(self) -> None:
2929
def mime_type(self) -> str:
3030
return f"audio/{self.format}"
3131

32-
def to_bytes(self) -> bytes:
32+
def to_bytes(self, **kwargs) -> bytes:
3333
return self.value
3434

3535
def to_text(self) -> str:

griptape/artifacts/base_artifact.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def __bool__(self) -> bool:
4949
def __len__(self) -> int:
5050
return len(self.value)
5151

52-
def to_bytes(self) -> bytes:
52+
def to_bytes(self, **kwargs) -> bytes:
5353
return self.to_text().encode(encoding=self.encoding, errors=self.encoding_error_handler)
5454

5555
@abstractmethod

griptape/artifacts/blob_artifact.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def base64(self) -> str:
2828
def mime_type(self) -> str:
2929
return "application/octet-stream"
3030

31-
def to_bytes(self) -> bytes:
31+
def to_bytes(self, **kwargs) -> bytes:
3232
return self.value
3333

3434
def to_text(self) -> str:

griptape/artifacts/image_artifact.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def __attrs_post_init__(self) -> None:
3333
def mime_type(self) -> str:
3434
return f"image/{self.format}"
3535

36-
def to_bytes(self) -> bytes:
36+
def to_bytes(self, **kwargs) -> bytes:
3737
return self.value
3838

3939
def to_text(self) -> str:
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from griptape.artifacts.url_artifact import UrlArtifact
2+
3+
4+
class ImageUrlArtifact(UrlArtifact):
5+
"""Stores a url to an image.
6+
7+
Attributes:
8+
value: The url.
9+
"""

griptape/artifacts/url_artifact.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from __future__ import annotations
2+
3+
import requests
4+
from attrs import define, field
5+
6+
from griptape.artifacts import BaseArtifact
7+
8+
9+
@define
10+
class UrlArtifact(BaseArtifact):
11+
"""Stores a url.
12+
13+
Attributes:
14+
value: The url.
15+
"""
16+
17+
value: str = field(metadata={"serializable": True})
18+
19+
def to_bytes(self, *, headers: dict | None = None, **kwargs) -> bytes:
20+
"""Fetches the content of the URL and returns it as bytes.
21+
22+
Args:
23+
headers: Optional headers to include in the request.
24+
**kwargs: Additional keyword arguments, not used.
25+
26+
Returns:
27+
bytes: The content of the URL as bytes.
28+
29+
"""
30+
response = requests.get(self.value, headers=headers)
31+
response.raise_for_status()
32+
33+
return response.content
34+
35+
def to_text(self) -> str:
36+
"""Returns the URL as is."""
37+
return self.value
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from griptape.artifacts import ImageUrlArtifact
2+
3+
4+
class TestImageUrlArtifact:
5+
def test_init(self):
6+
assert ImageUrlArtifact(
7+
value="some url",
8+
)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from unittest.mock import Mock
2+
3+
import pytest
4+
5+
from griptape.artifacts import BaseArtifact, UrlArtifact
6+
7+
8+
class TestUrlArtifact:
9+
@pytest.fixture()
10+
def url_artifact(self):
11+
return UrlArtifact(
12+
value="some url",
13+
)
14+
15+
@pytest.fixture(autouse=True)
16+
def mock_get(self, mocker):
17+
mock_response = Mock(content=b"some binary data", status_code=200)
18+
19+
return mocker.patch("requests.get", return_value=mock_response)
20+
21+
def test_to_text(self, url_artifact: UrlArtifact):
22+
assert url_artifact.to_text() == "some url"
23+
24+
def test_to_dict(self, url_artifact: UrlArtifact):
25+
image_dict = url_artifact.to_dict()
26+
27+
assert image_dict["value"] == "some url"
28+
29+
def test_deserialization(self, url_artifact):
30+
artifact_dict = url_artifact.to_dict()
31+
deserialized_artifact = BaseArtifact.from_dict(artifact_dict)
32+
33+
assert isinstance(deserialized_artifact, UrlArtifact)
34+
35+
assert deserialized_artifact.value == "some url"
36+
37+
@pytest.mark.parametrize(
38+
"headers",
39+
[
40+
None,
41+
{},
42+
{"Authorization": "Bearer some_token"},
43+
],
44+
)
45+
def test_to_bytes(self, url_artifact, headers):
46+
assert url_artifact.to_bytes(headers=headers) == b"some binary data"

0 commit comments

Comments
 (0)