Skip to content

Commit b006923

Browse files
authored
Finish up caching for Python (#499)
2 parents 3027324 + 0f671ea commit b006923

File tree

13 files changed

+317
-95
lines changed

13 files changed

+317
-95
lines changed

python/example-pytest-selfie/pyproject.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ readme = "README.md"
1313
requires-python = ">=3.9"
1414
dependencies = [
1515
"flask>=3.0.3",
16-
"openai>=1.0.0",
1716
]
1817

1918
[tool.uv.sources]
@@ -24,9 +23,11 @@ pytest-selfie = { path = "../pytest-selfie", editable = true }
2423
dev = [
2524
"beautifulsoup4>=4.12.3",
2625
"markdownify>=0.12.1",
26+
"openai>=1.0.0",
2727
"pyright>=1.1.350",
2828
"pytest-selfie>=0.1.0",
2929
"pytest>=8.0.0",
30+
"requests>=2.32.3",
3031
"ruff>=0.5.0",
3132
"selfie-lib>=0.1.0",
3233
"werkzeug>=3.0.3",
1.99 MB
Loading

python/example-pytest-selfie/test.jpg

-1
This file was deleted.

python/example-pytest-selfie/tests/cache_selfie_test.py

-12
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import os
2+
import random
3+
4+
import requests
5+
from openai import OpenAI
6+
from selfie_lib import cache_selfie, cache_selfie_binary, cache_selfie_json
7+
8+
9+
def test_cache_selfie():
10+
cache_selfie(lambda: str(random.random())).to_be("0.06699295946441819")
11+
12+
13+
def openai():
14+
return OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
15+
16+
17+
def test_gen_ai():
18+
# Fetch the chat response with caching
19+
chat: dict = cache_selfie_json(
20+
lambda: openai()
21+
.chat.completions.create(
22+
model="gpt-4o",
23+
messages=[
24+
{
25+
"role": "user",
26+
"content": "Expressive but brief language describing a robot creating a self portrait.",
27+
}
28+
],
29+
)
30+
.to_dict()
31+
).to_be("""{
32+
"id": "chatcmpl-Af1Nf34netAfGW7ZIQArEHavfuYtg",
33+
"choices": [
34+
{
35+
"finish_reason": "stop",
36+
"index": 0,
37+
"logprobs": null,
38+
"message": {
39+
"content": "A sleek robot, its mechanical fingers dancing with precision, deftly wields a brush against the canvas. Whirs and clicks echo softly as vibrant strokes emerge, each infused with an unexpected soulfulness. Metal meets art as synthetic imagination captures its own intricate reflection\\u2014a symphony of circuitry bathed in delicate hues.",
40+
"refusal": null,
41+
"role": "assistant"
42+
}
43+
}
44+
],
45+
"created": 1734340119,
46+
"model": "gpt-4o-2024-08-06",
47+
"object": "chat.completion",
48+
"system_fingerprint": "fp_9faba9f038",
49+
"usage": {
50+
"completion_tokens": 62,
51+
"prompt_tokens": 20,
52+
"total_tokens": 82,
53+
"completion_tokens_details": {
54+
"accepted_prediction_tokens": 0,
55+
"audio_tokens": 0,
56+
"reasoning_tokens": 0,
57+
"rejected_prediction_tokens": 0
58+
},
59+
"prompt_tokens_details": {
60+
"audio_tokens": 0,
61+
"cached_tokens": 0
62+
}
63+
}
64+
}""")
65+
# raise ValueError(f"KEYS={chat.keys()} TYPE={type(chat)}")
66+
image_url: dict = cache_selfie_json(
67+
lambda: openai()
68+
.images.generate(
69+
model="dall-e-3", prompt=chat["choices"][0]["message"]["content"]
70+
)
71+
.to_dict()
72+
).to_be("""{
73+
"created": 1734340142,
74+
"data": [
75+
{
76+
"revised_prompt": "Visualize a sleek robot adorned in a metallic shell. Its highly precise mechanical digits engage rhythmically with a paintbrush, swirling it flawlessly over a robust canvas. The environment is immersed in resonating mechanical sounds blended with the aura of creativity unfurling. Strikingly vivid strokes of paint materialize from the robot's calculated artistry, each stroke conveying a depth and emotion unanticipated of a mechanical entity. This metallic artist exhibits its self-inspired art by meticulously crafting its own intricate reflection\\u2014an orchestra of electronics bathed in a palette of gentle colors.",
77+
"url": "https://oaidalleapiprodscus.blob.core.windows.net/private/org-SUepmbCtftBix3RViJYKuYKY/user-KFRqcsnjZPSTulNaxrY5wjL3/img-JVxDCOAuLoIky3ucNNJWo7fG.png?st=2024-12-16T08%3A09%3A02Z&se=2024-12-16T10%3A09%3A02Z&sp=r&sv=2024-08-04&sr=b&rscd=inline&rsct=image/png&skoid=d505667d-d6c1-4a0a-bac7-5c84a87759f8&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2024-12-16T00%3A47%3A43Z&ske=2024-12-17T00%3A47%3A43Z&sks=b&skv=2024-08-04&sig=nIiMMZBNnqPO2jblJ8pDvWS2AFTOaicAWAD6BDqP9jU%3D"
78+
}
79+
]
80+
}""")
81+
82+
url = image_url["data"][0]["url"]
83+
cache_selfie_binary(lambda: requests.get(url).content).to_be_file(
84+
"self-portrait.png"
85+
)

python/example-pytest-selfie/uv.lock

+113-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

python/selfie-lib/selfie_lib/CacheSelfie.py

+30
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,36 @@ def cache_selfie(
3434
raise TypeError("Invalid arguments provided to cache_selfie")
3535

3636

37+
def cache_selfie_json(to_cache: Callable[..., T]) -> "CacheSelfie[T]":
38+
return cache_selfie(to_cache, Roundtrip.json())
39+
40+
41+
@overload
42+
def cache_selfie_binary(
43+
to_cache: Callable[..., bytes],
44+
) -> "CacheSelfieBinary[bytes]": ...
45+
46+
47+
@overload
48+
def cache_selfie_binary(
49+
to_cache: Callable[..., T], roundtrip: Roundtrip[T, bytes]
50+
) -> "CacheSelfieBinary[T]": ...
51+
52+
53+
def cache_selfie_binary(
54+
to_cache: Union[Callable[..., bytes], Callable[..., T]],
55+
roundtrip: Optional[Roundtrip[T, bytes]] = None,
56+
) -> Union["CacheSelfieBinary[bytes]", "CacheSelfieBinary[T]"]:
57+
if roundtrip is None:
58+
# the cacheable better be a bytes!
59+
return cache_selfie_binary(to_cache, Roundtrip.identity()) # type: ignore
60+
elif isinstance(roundtrip, Roundtrip) and to_cache is not None:
61+
deferred_disk_storage = _selfieSystem().disk_thread_local()
62+
return CacheSelfieBinary(deferred_disk_storage, roundtrip, to_cache) # type: ignore
63+
else:
64+
raise TypeError("Invalid arguments provided to cache_selfie")
65+
66+
3767
class CacheSelfie(Generic[T]):
3868
def __init__(
3969
self,

python/selfie-lib/selfie_lib/Roundtrip.py

+14
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,17 @@ def parse(self, serialized: Any) -> Any:
2626
return serialized
2727

2828
return Identity()
29+
30+
@classmethod
31+
def json(cls) -> "Roundtrip[T, str]":
32+
"""Return a Roundtrip that serializes to/from JSON strings."""
33+
import json
34+
35+
class JsonRoundtrip(Roundtrip[Any, str]):
36+
def serialize(self, value: Any) -> str:
37+
return json.dumps(value, indent=4)
38+
39+
def parse(self, serialized: str) -> Any:
40+
return json.loads(serialized)
41+
42+
return JsonRoundtrip()

python/selfie-lib/selfie_lib/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from .ArrayMap import ArraySet as ArraySet
44
from .Atomic import AtomicReference as AtomicReference
55
from .CacheSelfie import cache_selfie as cache_selfie
6+
from .CacheSelfie import cache_selfie_binary as cache_selfie_binary
7+
from .CacheSelfie import cache_selfie_json as cache_selfie_json
68
from .CommentTracker import CommentTracker as CommentTracker
79
from .FS import FS as FS
810
from .Lens import Camera as Camera

selfie.dev/public/dalle-3-jvm.webp

42.5 KB
Binary file not shown.

selfie.dev/public/dalle-3-py.webp

50.3 KB
Binary file not shown.

selfie.dev/src/pages/jvm/cache.mdx

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,6 @@ cacheSelfieBinary { HttpClient().request(images[0].url).readBytes() }
119119

120120
Since we used `toBeFile`, we can open `com/example/kotest/dalle-3.png` in Mac Preview / Windows Explorer.
121121

122-
<img alt="Robot self portrait" src="/dalle-3.webp" width="400px"/>
122+
<img alt="Robot self portrait" src="/dalle-3-jvm.webp" width="400px"/>
123123

124124
*Pull requests to improve the landing page and documentation are greatly appreciated, you can find the [source code here](https://github.com/diffplug/selfie).*

0 commit comments

Comments
 (0)