pixie-prompts 0.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pixie/prompts/__init__.py +0 -0
- pixie/prompts/graphql.py +205 -0
- pixie/prompts/prompt.py +373 -0
- pixie/prompts/prompt_management.py +82 -0
- pixie/prompts/server.py +263 -0
- pixie/prompts/storage.py +228 -0
- pixie/tests/__init__.py +0 -0
- pixie/tests/test_prompt.py +1321 -0
- pixie/tests/test_prompt_management.py +117 -0
- pixie/tests/test_prompt_storage.py +1453 -0
- pixie_prompts-0.0.0.dist-info/METADATA +35 -0
- pixie_prompts-0.0.0.dist-info/RECORD +15 -0
- pixie_prompts-0.0.0.dist-info/WHEEL +4 -0
- pixie_prompts-0.0.0.dist-info/entry_points.txt +3 -0
- pixie_prompts-0.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,1453 @@
|
|
|
1
|
+
"""Comprehensive unit tests for pixie.prompts.storage module."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
import pytest
|
|
7
|
+
from types import NoneType
|
|
8
|
+
from typing import Dict
|
|
9
|
+
|
|
10
|
+
from pixie.prompts.prompt import BaseUntypedPrompt, BasePrompt, _prompt_registry
|
|
11
|
+
from pixie.prompts.storage import _FilePromptStorage
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestFilePromptStorage:
|
|
15
|
+
"""Tests for FilePromptStorage class."""
|
|
16
|
+
|
|
17
|
+
@pytest.fixture(autouse=True)
|
|
18
|
+
def clear_prompt_registry(self):
|
|
19
|
+
"""Clear the global prompt registry before each test."""
|
|
20
|
+
_prompt_registry.clear()
|
|
21
|
+
|
|
22
|
+
@pytest.fixture(autouse=True)
|
|
23
|
+
def reset_storage_instance(self):
|
|
24
|
+
"""Reset the global storage instance before each test."""
|
|
25
|
+
import pixie.prompts.storage as storage_module
|
|
26
|
+
|
|
27
|
+
storage_module._storage_instance = None
|
|
28
|
+
yield
|
|
29
|
+
storage_module._storage_instance = None
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def temp_dir(self):
|
|
33
|
+
"""Create a temporary directory for testing."""
|
|
34
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
35
|
+
yield tmpdir
|
|
36
|
+
|
|
37
|
+
@pytest.fixture
|
|
38
|
+
def sample_prompt_data(self) -> Dict[str, Dict]:
|
|
39
|
+
"""Sample prompt data for testing."""
|
|
40
|
+
return {
|
|
41
|
+
"prompt1": {
|
|
42
|
+
"versions": {"v1": "Hello {name}", "v2": "Hi {name}"},
|
|
43
|
+
"defaultVersionId": "v1",
|
|
44
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
45
|
+
},
|
|
46
|
+
"prompt2": {
|
|
47
|
+
"versions": {"default": "Goodbye {name}"},
|
|
48
|
+
"defaultVersionId": "default",
|
|
49
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
def create_sample_files(self, temp_dir: str, sample_data: Dict[str, Dict]):
|
|
54
|
+
"""Create sample JSON files in the temp directory."""
|
|
55
|
+
for prompt_id, data in sample_data.items():
|
|
56
|
+
filepath = os.path.join(temp_dir, f"{prompt_id}.json")
|
|
57
|
+
with open(filepath, "w") as f:
|
|
58
|
+
json.dump(data, f)
|
|
59
|
+
|
|
60
|
+
def test_init_creates_directory_if_not_exists(self, temp_dir: str):
|
|
61
|
+
"""Test that __init__ creates the directory if it doesn't exist."""
|
|
62
|
+
subdir = os.path.join(temp_dir, "storage")
|
|
63
|
+
assert not os.path.exists(subdir)
|
|
64
|
+
|
|
65
|
+
storage = _FilePromptStorage(subdir)
|
|
66
|
+
assert os.path.exists(subdir)
|
|
67
|
+
assert isinstance(storage._prompts, dict)
|
|
68
|
+
assert len(storage._prompts) == 0
|
|
69
|
+
|
|
70
|
+
@pytest.mark.asyncio
|
|
71
|
+
async def test_init_loads_existing_files(
|
|
72
|
+
self, temp_dir: str, sample_prompt_data: Dict[str, Dict]
|
|
73
|
+
):
|
|
74
|
+
"""Test that __init__ loads existing JSON files into memory."""
|
|
75
|
+
self.create_sample_files(temp_dir, sample_prompt_data)
|
|
76
|
+
|
|
77
|
+
storage = _FilePromptStorage(temp_dir)
|
|
78
|
+
|
|
79
|
+
assert len(storage._prompts) == 2
|
|
80
|
+
assert "prompt1" in storage._prompts
|
|
81
|
+
assert "prompt2" in storage._prompts
|
|
82
|
+
|
|
83
|
+
prompt1 = storage._prompts["prompt1"]
|
|
84
|
+
assert isinstance(prompt1, BaseUntypedPrompt)
|
|
85
|
+
assert prompt1.id == "prompt1"
|
|
86
|
+
assert prompt1.get_versions() == sample_prompt_data["prompt1"]["versions"]
|
|
87
|
+
assert (
|
|
88
|
+
prompt1.get_default_version_id()
|
|
89
|
+
== sample_prompt_data["prompt1"]["defaultVersionId"]
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
prompt2 = storage._prompts["prompt2"]
|
|
93
|
+
assert prompt2.id == "prompt2"
|
|
94
|
+
assert prompt2.get_versions() == sample_prompt_data["prompt2"]["versions"]
|
|
95
|
+
assert (
|
|
96
|
+
prompt2.get_default_version_id()
|
|
97
|
+
== sample_prompt_data["prompt2"]["defaultVersionId"]
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def test_init_handles_empty_directory(self, temp_dir: str):
|
|
101
|
+
"""Test that __init__ handles an empty directory gracefully."""
|
|
102
|
+
storage = _FilePromptStorage(temp_dir)
|
|
103
|
+
assert len(storage._prompts) == 0
|
|
104
|
+
|
|
105
|
+
def test_init_skips_non_json_files(self, temp_dir: str):
|
|
106
|
+
"""Test that __init__ skips files that don't end with .json."""
|
|
107
|
+
# Create a JSON file and a non-JSON file
|
|
108
|
+
json_path = os.path.join(temp_dir, "prompt1.json")
|
|
109
|
+
with open(json_path, "w") as f:
|
|
110
|
+
json.dump(
|
|
111
|
+
{
|
|
112
|
+
"versions": {"default": "test"},
|
|
113
|
+
"defaultVersionId": "default",
|
|
114
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
115
|
+
},
|
|
116
|
+
f,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
txt_path = os.path.join(temp_dir, "readme.txt")
|
|
120
|
+
with open(txt_path, "w") as f:
|
|
121
|
+
f.write("This is not JSON")
|
|
122
|
+
|
|
123
|
+
storage = _FilePromptStorage(temp_dir)
|
|
124
|
+
assert len(storage._prompts) == 1
|
|
125
|
+
assert "prompt1" in storage._prompts
|
|
126
|
+
|
|
127
|
+
def test_init_handles_invalid_json(self, temp_dir: str):
|
|
128
|
+
"""Test that __init__ raises an exception for invalid JSON."""
|
|
129
|
+
invalid_path = os.path.join(temp_dir, "invalid.json")
|
|
130
|
+
with open(invalid_path, "w") as f:
|
|
131
|
+
f.write("invalid json content")
|
|
132
|
+
|
|
133
|
+
with pytest.raises(json.JSONDecodeError):
|
|
134
|
+
_FilePromptStorage(temp_dir)
|
|
135
|
+
|
|
136
|
+
def test_init_handles_missing_versions(self, temp_dir: str):
|
|
137
|
+
"""Test that __init__ raises ValueError for missing versions in JSON."""
|
|
138
|
+
missing_versions_path = os.path.join(temp_dir, "missing.json")
|
|
139
|
+
with open(missing_versions_path, "w") as f:
|
|
140
|
+
json.dump({"defaultVersionId": "default"}, f)
|
|
141
|
+
|
|
142
|
+
with pytest.raises(KeyError):
|
|
143
|
+
_FilePromptStorage(temp_dir)
|
|
144
|
+
|
|
145
|
+
@pytest.mark.asyncio
|
|
146
|
+
async def test_exists_returns_true_for_existing_prompt(
|
|
147
|
+
self, temp_dir: str, sample_prompt_data: Dict[str, Dict]
|
|
148
|
+
):
|
|
149
|
+
"""Test that exists returns True for existing prompts."""
|
|
150
|
+
self.create_sample_files(temp_dir, sample_prompt_data)
|
|
151
|
+
storage = _FilePromptStorage(temp_dir)
|
|
152
|
+
|
|
153
|
+
assert storage.exists("prompt1") is True
|
|
154
|
+
assert storage.exists("prompt2") is True
|
|
155
|
+
|
|
156
|
+
@pytest.mark.asyncio
|
|
157
|
+
async def test_exists_returns_false_for_non_existing_prompt(self, temp_dir: str):
|
|
158
|
+
"""Test that exists returns False for non-existing prompts."""
|
|
159
|
+
storage = _FilePromptStorage(temp_dir)
|
|
160
|
+
|
|
161
|
+
assert storage.exists("nonexistent") is False
|
|
162
|
+
|
|
163
|
+
@pytest.mark.asyncio
|
|
164
|
+
async def test_save_creates_new_prompt(self, temp_dir: str):
|
|
165
|
+
"""Test that save creates a new prompt and returns True."""
|
|
166
|
+
storage = _FilePromptStorage(temp_dir)
|
|
167
|
+
|
|
168
|
+
prompt = BaseUntypedPrompt(
|
|
169
|
+
versions={"v1": "Hello {name}", "v2": "Hi {name}"},
|
|
170
|
+
default_version_id="v1",
|
|
171
|
+
id="new_prompt",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Save should work for new prompts now and return True
|
|
175
|
+
result = storage.save(prompt)
|
|
176
|
+
assert result is True
|
|
177
|
+
|
|
178
|
+
# Verify file was created
|
|
179
|
+
filepath = os.path.join(temp_dir, "new_prompt.json")
|
|
180
|
+
assert os.path.exists(filepath)
|
|
181
|
+
|
|
182
|
+
# Verify content
|
|
183
|
+
with open(filepath, "r") as f:
|
|
184
|
+
data = json.load(f)
|
|
185
|
+
assert data["versions"] == {"v1": "Hello {name}", "v2": "Hi {name}"}
|
|
186
|
+
assert data["defaultVersionId"] == "v1"
|
|
187
|
+
assert "variablesSchema" in data
|
|
188
|
+
|
|
189
|
+
@pytest.mark.asyncio
|
|
190
|
+
async def test_save_updates_existing_prompt(
|
|
191
|
+
self, temp_dir: str, sample_prompt_data: Dict[str, Dict]
|
|
192
|
+
):
|
|
193
|
+
"""Test that save updates an existing prompt and returns False."""
|
|
194
|
+
self.create_sample_files(temp_dir, sample_prompt_data)
|
|
195
|
+
storage = _FilePromptStorage(temp_dir)
|
|
196
|
+
|
|
197
|
+
# Register the loaded prompts
|
|
198
|
+
from pixie.prompts.prompt import BasePrompt
|
|
199
|
+
|
|
200
|
+
for p in storage._prompts.values():
|
|
201
|
+
BasePrompt.from_untyped(p)
|
|
202
|
+
|
|
203
|
+
# Modify the existing prompt
|
|
204
|
+
storage._prompts["prompt1"]
|
|
205
|
+
updated_versions = {"v1": "Updated {name}", "v3": "New version"}
|
|
206
|
+
updated_prompt = BaseUntypedPrompt(
|
|
207
|
+
versions=updated_versions, default_version_id="v1", id="prompt1"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
result = storage.save(updated_prompt)
|
|
211
|
+
assert result is False # Should return False for existing prompt
|
|
212
|
+
|
|
213
|
+
# Check in-memory was updated
|
|
214
|
+
assert storage._prompts["prompt1"] is updated_prompt
|
|
215
|
+
assert storage._prompts["prompt1"].get_versions() == updated_versions
|
|
216
|
+
|
|
217
|
+
# Check file was updated
|
|
218
|
+
filepath = os.path.join(temp_dir, "prompt1.json")
|
|
219
|
+
with open(filepath, "r") as f:
|
|
220
|
+
data = json.load(f)
|
|
221
|
+
assert data["versions"] == updated_versions
|
|
222
|
+
assert data["defaultVersionId"] == "v1"
|
|
223
|
+
|
|
224
|
+
@pytest.mark.asyncio
|
|
225
|
+
async def test_get_returns_existing_prompt(
|
|
226
|
+
self, temp_dir: str, sample_prompt_data: Dict[str, Dict]
|
|
227
|
+
):
|
|
228
|
+
"""Test that get returns the correct prompt for existing ID."""
|
|
229
|
+
self.create_sample_files(temp_dir, sample_prompt_data)
|
|
230
|
+
storage = _FilePromptStorage(temp_dir)
|
|
231
|
+
|
|
232
|
+
prompt = storage.get("prompt1")
|
|
233
|
+
assert isinstance(prompt, BaseUntypedPrompt)
|
|
234
|
+
assert prompt.id == "prompt1"
|
|
235
|
+
assert prompt.get_versions() == sample_prompt_data["prompt1"]["versions"]
|
|
236
|
+
assert (
|
|
237
|
+
prompt.get_default_version_id()
|
|
238
|
+
== sample_prompt_data["prompt1"]["defaultVersionId"]
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
@pytest.mark.asyncio
|
|
242
|
+
async def test_get_raises_keyerror_for_non_existing_prompt(self, temp_dir: str):
|
|
243
|
+
"""Test that get raises KeyError for non-existing prompt ID."""
|
|
244
|
+
storage = _FilePromptStorage(temp_dir)
|
|
245
|
+
|
|
246
|
+
with pytest.raises(KeyError):
|
|
247
|
+
storage.get("nonexistent")
|
|
248
|
+
|
|
249
|
+
@pytest.mark.asyncio
|
|
250
|
+
async def test_save_writes_to_file_before_memory_update(self, temp_dir: str):
|
|
251
|
+
"""Test that save creates a new prompt successfully."""
|
|
252
|
+
storage = _FilePromptStorage(temp_dir)
|
|
253
|
+
|
|
254
|
+
prompt = BaseUntypedPrompt(
|
|
255
|
+
versions={"default": "Test"}, default_version_id="default", id="test_prompt"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
result = storage.save(prompt)
|
|
259
|
+
assert result is True
|
|
260
|
+
|
|
261
|
+
# Verify it was saved to file
|
|
262
|
+
filepath = os.path.join(temp_dir, "test_prompt.json")
|
|
263
|
+
assert os.path.exists(filepath)
|
|
264
|
+
|
|
265
|
+
@pytest.mark.asyncio
|
|
266
|
+
async def test_init_with_default_version_id_none(self, temp_dir: str):
|
|
267
|
+
"""Test loading a prompt where defaultVersionId is missing (defaults to first version)."""
|
|
268
|
+
filepath = os.path.join(temp_dir, "prompt.json")
|
|
269
|
+
with open(filepath, "w") as f:
|
|
270
|
+
json.dump(
|
|
271
|
+
{
|
|
272
|
+
"versions": {"v1": "Version 1"},
|
|
273
|
+
"defaultVersionId": "v1",
|
|
274
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
275
|
+
},
|
|
276
|
+
f,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
storage = _FilePromptStorage(temp_dir)
|
|
280
|
+
prompt = storage._prompts["prompt"]
|
|
281
|
+
assert prompt.get_default_version_id() == "v1" # Defaults to first version
|
|
282
|
+
|
|
283
|
+
@pytest.mark.asyncio
|
|
284
|
+
async def test_save_validates_schema_compatibility(self, temp_dir: str):
|
|
285
|
+
"""Test that save validates schema compatibility when updating prompts."""
|
|
286
|
+
# Create initial prompt with schema
|
|
287
|
+
initial_prompt = BaseUntypedPrompt(
|
|
288
|
+
versions={"v1": "Hello"},
|
|
289
|
+
default_version_id="v1",
|
|
290
|
+
id="schema_test",
|
|
291
|
+
variables_schema={
|
|
292
|
+
"type": "object",
|
|
293
|
+
"properties": {"name": {"type": "string"}},
|
|
294
|
+
"required": ["name"],
|
|
295
|
+
},
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
storage = _FilePromptStorage(temp_dir)
|
|
299
|
+
storage.save(initial_prompt)
|
|
300
|
+
|
|
301
|
+
# Try to update with incompatible schema (removing required field)
|
|
302
|
+
updated_prompt = BaseUntypedPrompt(
|
|
303
|
+
versions={"v1": "Hello"},
|
|
304
|
+
default_version_id="v1",
|
|
305
|
+
id="schema_test",
|
|
306
|
+
variables_schema={
|
|
307
|
+
"type": "object",
|
|
308
|
+
"properties": {"age": {"type": "integer"}},
|
|
309
|
+
"required": ["age"],
|
|
310
|
+
},
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Should raise TypeError due to incompatible schema
|
|
314
|
+
with pytest.raises(TypeError):
|
|
315
|
+
storage.save(updated_prompt)
|
|
316
|
+
|
|
317
|
+
@pytest.mark.asyncio
|
|
318
|
+
async def test_save_allows_compatible_schema_extension(self, temp_dir: str):
|
|
319
|
+
"""Test that save allows extending schema with compatible changes."""
|
|
320
|
+
# Create initial prompt with broader schema
|
|
321
|
+
initial_prompt = BaseUntypedPrompt(
|
|
322
|
+
versions={"v1": "Hello"},
|
|
323
|
+
default_version_id="v1",
|
|
324
|
+
id="schema_test",
|
|
325
|
+
variables_schema={
|
|
326
|
+
"type": "object",
|
|
327
|
+
"properties": {
|
|
328
|
+
"name": {"type": "string"},
|
|
329
|
+
"age": {"type": "integer"},
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
storage = _FilePromptStorage(temp_dir)
|
|
335
|
+
storage.save(initial_prompt)
|
|
336
|
+
|
|
337
|
+
# Update with narrower but compatible schema (fewer fields)
|
|
338
|
+
# Original schema is a subschema of new schema if new schema is more permissive
|
|
339
|
+
updated_prompt = BaseUntypedPrompt(
|
|
340
|
+
versions={"v1": "Hello"},
|
|
341
|
+
default_version_id="v1",
|
|
342
|
+
id="schema_test",
|
|
343
|
+
variables_schema={
|
|
344
|
+
"type": "object",
|
|
345
|
+
"properties": {"name": {"type": "string"}},
|
|
346
|
+
},
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# Should succeed - removing optional fields makes schema more permissive
|
|
350
|
+
result = storage.save(updated_prompt)
|
|
351
|
+
assert result is False # Existing prompt
|
|
352
|
+
|
|
353
|
+
@pytest.mark.asyncio
|
|
354
|
+
async def test_storage_backed_prompt_lazy_loading(self, temp_dir: str):
|
|
355
|
+
"""Test that StorageBackedPrompt loads from storage on first access."""
|
|
356
|
+
from pixie.prompts.storage import (
|
|
357
|
+
initialize_prompt_storage,
|
|
358
|
+
StorageBackedPrompt,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
# Create prompt file directly
|
|
362
|
+
import json
|
|
363
|
+
import os
|
|
364
|
+
|
|
365
|
+
prompt_file = os.path.join(temp_dir, "test_prompt.json")
|
|
366
|
+
with open(prompt_file, "w") as f:
|
|
367
|
+
json.dump(
|
|
368
|
+
{
|
|
369
|
+
"versions": {"v1": "Hello {name}"},
|
|
370
|
+
"defaultVersionId": "v1",
|
|
371
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
372
|
+
},
|
|
373
|
+
f,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# Initialize storage - it will load existing files
|
|
377
|
+
initialize_prompt_storage(temp_dir)
|
|
378
|
+
|
|
379
|
+
# Create StorageBackedPrompt - should not load yet
|
|
380
|
+
backed_prompt = StorageBackedPrompt(id="test_prompt")
|
|
381
|
+
assert backed_prompt._prompt is None
|
|
382
|
+
|
|
383
|
+
# Access versions - should trigger loading
|
|
384
|
+
versions = backed_prompt.get_versions()
|
|
385
|
+
assert versions == {"v1": "Hello {name}"}
|
|
386
|
+
assert backed_prompt._prompt is not None
|
|
387
|
+
|
|
388
|
+
@pytest.mark.asyncio
|
|
389
|
+
async def test_storage_backed_prompt_compile(self, temp_dir: str):
|
|
390
|
+
"""Test that StorageBackedPrompt.compile works correctly."""
|
|
391
|
+
from pixie.prompts.storage import (
|
|
392
|
+
initialize_prompt_storage,
|
|
393
|
+
StorageBackedPrompt,
|
|
394
|
+
)
|
|
395
|
+
from pixie.prompts.prompt import PromptVariables
|
|
396
|
+
|
|
397
|
+
class TestVars(PromptVariables):
|
|
398
|
+
name: str
|
|
399
|
+
|
|
400
|
+
# Create prompt file directly
|
|
401
|
+
import json
|
|
402
|
+
import os
|
|
403
|
+
|
|
404
|
+
prompt_file = os.path.join(temp_dir, "test_prompt.json")
|
|
405
|
+
with open(prompt_file, "w") as f:
|
|
406
|
+
json.dump(
|
|
407
|
+
{
|
|
408
|
+
"versions": {"v1": "Hello {{name}}!"},
|
|
409
|
+
"defaultVersionId": "v1",
|
|
410
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
411
|
+
},
|
|
412
|
+
f,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# Initialize storage
|
|
416
|
+
initialize_prompt_storage(temp_dir)
|
|
417
|
+
|
|
418
|
+
# Create StorageBackedPrompt with variable definition
|
|
419
|
+
backed_prompt = StorageBackedPrompt(
|
|
420
|
+
id="test_prompt", variables_definition=TestVars
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
# Compile
|
|
424
|
+
variables = TestVars(name="World")
|
|
425
|
+
result = backed_prompt.compile(variables)
|
|
426
|
+
assert result == "Hello World!"
|
|
427
|
+
|
|
428
|
+
@pytest.mark.asyncio
|
|
429
|
+
async def test_storage_backed_prompt_raises_without_init(self):
|
|
430
|
+
"""Test that StorageBackedPrompt raises error if storage not initialized."""
|
|
431
|
+
from pixie.prompts.storage import StorageBackedPrompt
|
|
432
|
+
import pixie.prompts.storage as storage_module
|
|
433
|
+
|
|
434
|
+
# Ensure storage is not initialized
|
|
435
|
+
storage_module._storage_instance = None
|
|
436
|
+
|
|
437
|
+
backed_prompt = StorageBackedPrompt(id="test_prompt")
|
|
438
|
+
|
|
439
|
+
with pytest.raises(
|
|
440
|
+
RuntimeError, match="Prompt storage has not been initialized"
|
|
441
|
+
):
|
|
442
|
+
backed_prompt.get_versions()
|
|
443
|
+
|
|
444
|
+
@pytest.mark.asyncio
|
|
445
|
+
async def test_create_prompt_helper(self, temp_dir: str):
|
|
446
|
+
"""Test the create_prompt helper function."""
|
|
447
|
+
from pixie.prompts.storage import initialize_prompt_storage
|
|
448
|
+
from pixie.prompts.prompt_management import create_prompt
|
|
449
|
+
|
|
450
|
+
# Create prompt file directly
|
|
451
|
+
import json
|
|
452
|
+
import os
|
|
453
|
+
|
|
454
|
+
prompt_file = os.path.join(temp_dir, "helper_test.json")
|
|
455
|
+
with open(prompt_file, "w") as f:
|
|
456
|
+
json.dump(
|
|
457
|
+
{
|
|
458
|
+
"versions": {"v1": "Test"},
|
|
459
|
+
"defaultVersionId": "v1",
|
|
460
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
461
|
+
},
|
|
462
|
+
f,
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
# Initialize storage
|
|
466
|
+
initialize_prompt_storage(temp_dir)
|
|
467
|
+
|
|
468
|
+
# Create prompt using helper
|
|
469
|
+
prompt = create_prompt(id="helper_test")
|
|
470
|
+
assert prompt.id == "helper_test"
|
|
471
|
+
|
|
472
|
+
versions = prompt.get_versions()
|
|
473
|
+
assert versions == {"v1": "Test"}
|
|
474
|
+
|
|
475
|
+
@pytest.mark.asyncio
|
|
476
|
+
async def test_storage_backed_prompt_schema_compatibility_check_passes(
|
|
477
|
+
self, temp_dir: str
|
|
478
|
+
):
|
|
479
|
+
"""Test that schema compatibility check passes when definition is subschema of storage."""
|
|
480
|
+
from pixie.prompts.storage import (
|
|
481
|
+
initialize_prompt_storage,
|
|
482
|
+
StorageBackedPrompt,
|
|
483
|
+
)
|
|
484
|
+
from pixie.prompts.prompt import PromptVariables
|
|
485
|
+
|
|
486
|
+
class TestVars(PromptVariables):
|
|
487
|
+
name: str
|
|
488
|
+
|
|
489
|
+
# Create prompt file with empty schema (accepts everything)
|
|
490
|
+
import json
|
|
491
|
+
import os
|
|
492
|
+
|
|
493
|
+
prompt_file = os.path.join(temp_dir, "schema_test.json")
|
|
494
|
+
with open(prompt_file, "w") as f:
|
|
495
|
+
json.dump(
|
|
496
|
+
{
|
|
497
|
+
"versions": {"v1": "Hello {name}!"},
|
|
498
|
+
"defaultVersionId": "v1",
|
|
499
|
+
"variablesSchema": {
|
|
500
|
+
"type": "object",
|
|
501
|
+
"properties": {},
|
|
502
|
+
}, # Empty schema
|
|
503
|
+
},
|
|
504
|
+
f,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
# Initialize storage
|
|
508
|
+
initialize_prompt_storage(temp_dir)
|
|
509
|
+
|
|
510
|
+
# Create StorageBackedPrompt with restrictive definition
|
|
511
|
+
backed_prompt = StorageBackedPrompt(
|
|
512
|
+
id="schema_test", variables_definition=TestVars
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
# Should not raise, since TestVars schema is subschema of empty
|
|
516
|
+
versions = backed_prompt.get_versions()
|
|
517
|
+
assert versions == {"v1": "Hello {name}!"}
|
|
518
|
+
|
|
519
|
+
@pytest.mark.asyncio
|
|
520
|
+
async def test_storage_backed_prompt_schema_compatibility_check_fails(
|
|
521
|
+
self, temp_dir: str
|
|
522
|
+
):
|
|
523
|
+
"""Test that schema compatibility check fails when definition is not subschema of storage."""
|
|
524
|
+
from pixie.prompts.storage import (
|
|
525
|
+
initialize_prompt_storage,
|
|
526
|
+
StorageBackedPrompt,
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
# Create prompt file with restrictive schema (requires name)
|
|
530
|
+
import json
|
|
531
|
+
import os
|
|
532
|
+
|
|
533
|
+
prompt_file = os.path.join(temp_dir, "schema_fail_test.json")
|
|
534
|
+
with open(prompt_file, "w") as f:
|
|
535
|
+
json.dump(
|
|
536
|
+
{
|
|
537
|
+
"versions": {"v1": "Hello {name}!"},
|
|
538
|
+
"defaultVersionId": "v1",
|
|
539
|
+
"variablesSchema": {
|
|
540
|
+
"type": "object",
|
|
541
|
+
"properties": {"name": {"type": "string"}},
|
|
542
|
+
"required": ["name"],
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
f,
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
# Initialize storage
|
|
549
|
+
initialize_prompt_storage(temp_dir)
|
|
550
|
+
|
|
551
|
+
# Create StorageBackedPrompt with NoneType (empty schema)
|
|
552
|
+
backed_prompt = StorageBackedPrompt(
|
|
553
|
+
id="schema_fail_test", variables_definition=NoneType
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
# Should raise TypeError because empty schema is not subschema of required schema
|
|
557
|
+
with pytest.raises(
|
|
558
|
+
TypeError,
|
|
559
|
+
match="The provided variables_definition is not compatible with the prompt's variables schema",
|
|
560
|
+
):
|
|
561
|
+
backed_prompt.get_versions()
|
|
562
|
+
|
|
563
|
+
@pytest.mark.asyncio
|
|
564
|
+
async def test_storage_backed_prompt_actualize(self, temp_dir: str):
|
|
565
|
+
"""Test that StorageBackedPrompt.actualize() loads the prompt and returns self."""
|
|
566
|
+
from pixie.prompts.storage import (
|
|
567
|
+
initialize_prompt_storage,
|
|
568
|
+
StorageBackedPrompt,
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
# Create prompt file directly
|
|
572
|
+
import json
|
|
573
|
+
import os
|
|
574
|
+
|
|
575
|
+
prompt_file = os.path.join(temp_dir, "actualize_test.json")
|
|
576
|
+
with open(prompt_file, "w") as f:
|
|
577
|
+
json.dump(
|
|
578
|
+
{
|
|
579
|
+
"versions": {"v1": "Hello {name}"},
|
|
580
|
+
"defaultVersionId": "v1",
|
|
581
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
582
|
+
},
|
|
583
|
+
f,
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
# Initialize storage
|
|
587
|
+
initialize_prompt_storage(temp_dir)
|
|
588
|
+
|
|
589
|
+
# Create StorageBackedPrompt - should not load yet
|
|
590
|
+
backed_prompt = StorageBackedPrompt(id="actualize_test")
|
|
591
|
+
assert backed_prompt._prompt is None
|
|
592
|
+
|
|
593
|
+
# Call actualize - should load and return self
|
|
594
|
+
result = backed_prompt.actualize()
|
|
595
|
+
assert result is backed_prompt
|
|
596
|
+
assert backed_prompt._prompt is not None
|
|
597
|
+
|
|
598
|
+
# Verify it works
|
|
599
|
+
versions = backed_prompt.get_versions()
|
|
600
|
+
assert versions == {"v1": "Hello {name}"}
|
|
601
|
+
|
|
602
|
+
@pytest.mark.asyncio
|
|
603
|
+
async def test_list_prompts_empty(self):
|
|
604
|
+
"""Test that list_prompts returns empty list initially."""
|
|
605
|
+
from pixie.prompts.prompt_management import list_prompts
|
|
606
|
+
import pixie.prompts.prompt_management as pm_module
|
|
607
|
+
|
|
608
|
+
# Clear the registry
|
|
609
|
+
pm_module._registry.clear()
|
|
610
|
+
|
|
611
|
+
prompts = list_prompts()
|
|
612
|
+
assert prompts == []
|
|
613
|
+
|
|
614
|
+
@pytest.mark.asyncio
|
|
615
|
+
async def test_get_prompt_nonexistent(self):
|
|
616
|
+
"""Test that get_prompt returns None for non-existent prompt."""
|
|
617
|
+
from pixie.prompts.prompt_management import get_prompt
|
|
618
|
+
import pixie.prompts.prompt_management as pm_module
|
|
619
|
+
|
|
620
|
+
# Clear the registry
|
|
621
|
+
pm_module._registry.clear()
|
|
622
|
+
|
|
623
|
+
prompt = get_prompt("nonexistent")
|
|
624
|
+
assert prompt is None
|
|
625
|
+
|
|
626
|
+
@pytest.mark.asyncio
|
|
627
|
+
async def test_create_prompt_new(self, temp_dir: str):
|
|
628
|
+
"""Test creating a new prompt with create_prompt."""
|
|
629
|
+
from pixie.prompts.storage import initialize_prompt_storage
|
|
630
|
+
from pixie.prompts.prompt_management import (
|
|
631
|
+
create_prompt,
|
|
632
|
+
get_prompt,
|
|
633
|
+
list_prompts,
|
|
634
|
+
)
|
|
635
|
+
import pixie.prompts.prompt_management as pm_module
|
|
636
|
+
|
|
637
|
+
# Clear the registry and initialize storage
|
|
638
|
+
pm_module._registry.clear()
|
|
639
|
+
initialize_prompt_storage(temp_dir)
|
|
640
|
+
|
|
641
|
+
# Create prompt file
|
|
642
|
+
import json
|
|
643
|
+
import os
|
|
644
|
+
|
|
645
|
+
prompt_file = os.path.join(temp_dir, "create_test.json")
|
|
646
|
+
with open(prompt_file, "w") as f:
|
|
647
|
+
json.dump(
|
|
648
|
+
{
|
|
649
|
+
"versions": {"v1": "Hello {name}"},
|
|
650
|
+
"defaultVersionId": "v1",
|
|
651
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
652
|
+
},
|
|
653
|
+
f,
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
# Create new prompt
|
|
657
|
+
prompt = create_prompt(id="create_test")
|
|
658
|
+
assert prompt.id == "create_test"
|
|
659
|
+
assert prompt.variables_definition == NoneType
|
|
660
|
+
|
|
661
|
+
# Should be in registry
|
|
662
|
+
prompt_with_registration = get_prompt("create_test")
|
|
663
|
+
assert prompt_with_registration is not None
|
|
664
|
+
retrieved = prompt_with_registration.prompt
|
|
665
|
+
assert retrieved is prompt
|
|
666
|
+
|
|
667
|
+
# Should be in list
|
|
668
|
+
prompts = list_prompts()
|
|
669
|
+
assert len(prompts) == 1
|
|
670
|
+
assert prompts[0].prompt is prompt
|
|
671
|
+
|
|
672
|
+
@pytest.mark.asyncio
|
|
673
|
+
async def test_create_prompt_existing_same_definition(self, temp_dir: str):
|
|
674
|
+
"""Test getting existing prompt with same variables_definition."""
|
|
675
|
+
from pixie.prompts.storage import initialize_prompt_storage
|
|
676
|
+
from pixie.prompts.prompt_management import create_prompt
|
|
677
|
+
import pixie.prompts.prompt_management as pm_module
|
|
678
|
+
from pixie.prompts.prompt import PromptVariables
|
|
679
|
+
|
|
680
|
+
class TestVars(PromptVariables):
|
|
681
|
+
name: str
|
|
682
|
+
|
|
683
|
+
# Clear the registry and initialize storage
|
|
684
|
+
pm_module._registry.clear()
|
|
685
|
+
initialize_prompt_storage(temp_dir)
|
|
686
|
+
|
|
687
|
+
# Create prompt file
|
|
688
|
+
import json
|
|
689
|
+
import os
|
|
690
|
+
|
|
691
|
+
prompt_file = os.path.join(temp_dir, "existing_test.json")
|
|
692
|
+
with open(prompt_file, "w") as f:
|
|
693
|
+
json.dump(
|
|
694
|
+
{
|
|
695
|
+
"versions": {"v1": "Hello {name}"},
|
|
696
|
+
"defaultVersionId": "v1",
|
|
697
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
698
|
+
},
|
|
699
|
+
f,
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
# Create prompt first time
|
|
703
|
+
prompt1 = create_prompt(id="existing_test", variables_definition=TestVars)
|
|
704
|
+
assert prompt1.variables_definition == TestVars
|
|
705
|
+
|
|
706
|
+
# Create same prompt second time - should return same instance
|
|
707
|
+
prompt2 = create_prompt(id="existing_test", variables_definition=TestVars)
|
|
708
|
+
assert prompt2 is prompt1
|
|
709
|
+
|
|
710
|
+
@pytest.mark.asyncio
|
|
711
|
+
async def test_create_prompt_existing_different_definition_raises(
|
|
712
|
+
self, temp_dir: str
|
|
713
|
+
):
|
|
714
|
+
"""Test that creating prompt with different variables_definition raises error."""
|
|
715
|
+
from pixie.prompts.storage import initialize_prompt_storage
|
|
716
|
+
from pixie.prompts.prompt_management import create_prompt
|
|
717
|
+
import pixie.prompts.prompt_management as pm_module
|
|
718
|
+
from pixie.prompts.prompt import PromptVariables
|
|
719
|
+
|
|
720
|
+
class TestVars1(PromptVariables):
|
|
721
|
+
name: str
|
|
722
|
+
|
|
723
|
+
class TestVars2(PromptVariables):
|
|
724
|
+
age: int
|
|
725
|
+
|
|
726
|
+
# Clear the registry and initialize storage
|
|
727
|
+
pm_module._registry.clear()
|
|
728
|
+
initialize_prompt_storage(temp_dir)
|
|
729
|
+
|
|
730
|
+
# Create prompt file
|
|
731
|
+
import json
|
|
732
|
+
import os
|
|
733
|
+
|
|
734
|
+
prompt_file = os.path.join(temp_dir, "conflict_test.json")
|
|
735
|
+
with open(prompt_file, "w") as f:
|
|
736
|
+
json.dump(
|
|
737
|
+
{
|
|
738
|
+
"versions": {"v1": "Hello {name}"},
|
|
739
|
+
"defaultVersionId": "v1",
|
|
740
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
741
|
+
},
|
|
742
|
+
f,
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
# Create prompt first time
|
|
746
|
+
create_prompt(id="conflict_test", variables_definition=TestVars1)
|
|
747
|
+
|
|
748
|
+
# Try to create with different definition - should raise
|
|
749
|
+
with pytest.raises(
|
|
750
|
+
ValueError,
|
|
751
|
+
match="Prompt with id 'conflict_test' already exists with a different variables definition",
|
|
752
|
+
):
|
|
753
|
+
create_prompt(id="conflict_test", variables_definition=TestVars2)
|
|
754
|
+
|
|
755
|
+
@pytest.mark.asyncio
|
|
756
|
+
async def test_storage_backed_prompt_properties(self, temp_dir: str):
|
|
757
|
+
"""Test StorageBackedPrompt id and variables_definition properties."""
|
|
758
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
759
|
+
from pixie.prompts.prompt import PromptVariables
|
|
760
|
+
|
|
761
|
+
class TestVars(PromptVariables):
|
|
762
|
+
name: str
|
|
763
|
+
|
|
764
|
+
initialize_prompt_storage(temp_dir)
|
|
765
|
+
|
|
766
|
+
prompt = StorageBackedPrompt(id="test_id", variables_definition=TestVars)
|
|
767
|
+
assert prompt.id == "test_id"
|
|
768
|
+
assert prompt.variables_definition == TestVars
|
|
769
|
+
|
|
770
|
+
@pytest.mark.asyncio
|
|
771
|
+
async def test_storage_backed_prompt_get_variables_schema(self, temp_dir: str):
|
|
772
|
+
"""Test StorageBackedPrompt.get_variables_schema."""
|
|
773
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
774
|
+
from pixie.prompts.prompt import PromptVariables
|
|
775
|
+
|
|
776
|
+
class TestVars(PromptVariables):
|
|
777
|
+
name: str
|
|
778
|
+
age: int
|
|
779
|
+
|
|
780
|
+
initialize_prompt_storage(temp_dir)
|
|
781
|
+
|
|
782
|
+
prompt = StorageBackedPrompt(id="test_id", variables_definition=TestVars)
|
|
783
|
+
schema = prompt.get_variables_schema()
|
|
784
|
+
assert schema == {
|
|
785
|
+
"type": "object",
|
|
786
|
+
"title": "TestVars",
|
|
787
|
+
"properties": {
|
|
788
|
+
"name": {"title": "Name", "type": "string"},
|
|
789
|
+
"age": {"title": "Age", "type": "integer"},
|
|
790
|
+
},
|
|
791
|
+
"required": ["name", "age"],
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
@pytest.mark.asyncio
|
|
795
|
+
async def test_storage_backed_prompt_exists_in_storage_true(self, temp_dir: str):
|
|
796
|
+
"""Test exists_in_storage returns True when prompt exists."""
|
|
797
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
798
|
+
|
|
799
|
+
# Create prompt file
|
|
800
|
+
import json
|
|
801
|
+
import os
|
|
802
|
+
|
|
803
|
+
prompt_file = os.path.join(temp_dir, "exists_test.json")
|
|
804
|
+
with open(prompt_file, "w") as f:
|
|
805
|
+
json.dump(
|
|
806
|
+
{
|
|
807
|
+
"versions": {"v1": "Hello"},
|
|
808
|
+
"defaultVersionId": "v1",
|
|
809
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
810
|
+
},
|
|
811
|
+
f,
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
initialize_prompt_storage(temp_dir)
|
|
815
|
+
|
|
816
|
+
prompt = StorageBackedPrompt(id="exists_test")
|
|
817
|
+
assert prompt.exists_in_storage() is True
|
|
818
|
+
|
|
819
|
+
@pytest.mark.asyncio
|
|
820
|
+
async def test_storage_backed_prompt_exists_in_storage_false(self, temp_dir: str):
|
|
821
|
+
"""Test exists_in_storage returns False when prompt does not exist."""
|
|
822
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
823
|
+
|
|
824
|
+
initialize_prompt_storage(temp_dir)
|
|
825
|
+
|
|
826
|
+
prompt = StorageBackedPrompt(id="nonexistent")
|
|
827
|
+
assert prompt.exists_in_storage() is False
|
|
828
|
+
|
|
829
|
+
@pytest.mark.asyncio
|
|
830
|
+
async def test_storage_backed_prompt_get_default_version_id(self, temp_dir: str):
|
|
831
|
+
"""Test StorageBackedPrompt.get_default_version_id."""
|
|
832
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
833
|
+
|
|
834
|
+
# Create prompt file
|
|
835
|
+
import json
|
|
836
|
+
import os
|
|
837
|
+
|
|
838
|
+
prompt_file = os.path.join(temp_dir, "default_test.json")
|
|
839
|
+
with open(prompt_file, "w") as f:
|
|
840
|
+
json.dump(
|
|
841
|
+
{
|
|
842
|
+
"versions": {"v1": "Version 1", "v2": "Version 2"},
|
|
843
|
+
"defaultVersionId": "v2",
|
|
844
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
845
|
+
},
|
|
846
|
+
f,
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
initialize_prompt_storage(temp_dir)
|
|
850
|
+
|
|
851
|
+
prompt = StorageBackedPrompt(id="default_test")
|
|
852
|
+
default_id = prompt.get_default_version_id()
|
|
853
|
+
assert default_id == "v2"
|
|
854
|
+
|
|
855
|
+
@pytest.mark.asyncio
|
|
856
|
+
async def test_storage_backed_prompt_append_version(self, temp_dir: str):
|
|
857
|
+
"""Test that StorageBackedPrompt.append_version works correctly."""
|
|
858
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
859
|
+
|
|
860
|
+
# Create prompt file
|
|
861
|
+
import json
|
|
862
|
+
import os
|
|
863
|
+
|
|
864
|
+
prompt_file = os.path.join(temp_dir, "append_test.json")
|
|
865
|
+
with open(prompt_file, "w") as f:
|
|
866
|
+
json.dump(
|
|
867
|
+
{
|
|
868
|
+
"versions": {"v1": "Hello {name}"},
|
|
869
|
+
"defaultVersionId": "v1",
|
|
870
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
871
|
+
},
|
|
872
|
+
f,
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
initialize_prompt_storage(temp_dir)
|
|
876
|
+
|
|
877
|
+
prompt = StorageBackedPrompt(id="append_test")
|
|
878
|
+
|
|
879
|
+
# Append new version
|
|
880
|
+
result_prompt = prompt.append_version(
|
|
881
|
+
version_id="v2", content="Hi {name}", set_as_default=True
|
|
882
|
+
)
|
|
883
|
+
|
|
884
|
+
# Check that it returns the underlying BasePrompt
|
|
885
|
+
assert isinstance(result_prompt, BasePrompt)
|
|
886
|
+
assert result_prompt.id == "append_test"
|
|
887
|
+
|
|
888
|
+
# Check versions were updated
|
|
889
|
+
versions = result_prompt.get_versions()
|
|
890
|
+
assert "v2" in versions
|
|
891
|
+
assert versions["v2"] == "Hi {name}"
|
|
892
|
+
assert result_prompt.get_default_version_id() == "v2"
|
|
893
|
+
|
|
894
|
+
# Check that storage was updated
|
|
895
|
+
storage = _FilePromptStorage(temp_dir)
|
|
896
|
+
stored_prompt = storage.get("append_test")
|
|
897
|
+
stored_versions = stored_prompt.get_versions()
|
|
898
|
+
assert "v2" in stored_versions
|
|
899
|
+
assert stored_versions["v2"] == "Hi {name}"
|
|
900
|
+
assert stored_prompt.get_default_version_id() == "v2"
|
|
901
|
+
|
|
902
|
+
@pytest.mark.asyncio
|
|
903
|
+
async def test_storage_backed_prompt_append_version_creates_new_prompt(
|
|
904
|
+
self, temp_dir: str
|
|
905
|
+
):
|
|
906
|
+
"""Test that StorageBackedPrompt.append_version creates a new prompt if it doesn't exist."""
|
|
907
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
908
|
+
|
|
909
|
+
initialize_prompt_storage(temp_dir)
|
|
910
|
+
|
|
911
|
+
prompt = StorageBackedPrompt(id="new_append_test")
|
|
912
|
+
|
|
913
|
+
# Append version to non-existing prompt (should create new)
|
|
914
|
+
result_prompt = prompt.append_version(
|
|
915
|
+
version_id="v1", content="Hello {name}", set_as_default=True
|
|
916
|
+
)
|
|
917
|
+
|
|
918
|
+
# Check that it returns the underlying BasePrompt
|
|
919
|
+
assert isinstance(result_prompt, BasePrompt)
|
|
920
|
+
assert result_prompt.id == "new_append_test"
|
|
921
|
+
|
|
922
|
+
# Check versions
|
|
923
|
+
versions = result_prompt.get_versions()
|
|
924
|
+
assert "v1" in versions
|
|
925
|
+
assert versions["v1"] == "Hello {name}"
|
|
926
|
+
assert result_prompt.get_default_version_id() == "v1"
|
|
927
|
+
|
|
928
|
+
# Check that storage was created
|
|
929
|
+
storage = _FilePromptStorage(temp_dir)
|
|
930
|
+
stored_prompt = storage.get("new_append_test")
|
|
931
|
+
stored_versions = stored_prompt.get_versions()
|
|
932
|
+
assert "v1" in stored_versions
|
|
933
|
+
assert stored_versions["v1"] == "Hello {name}"
|
|
934
|
+
assert stored_prompt.get_default_version_id() == "v1"
|
|
935
|
+
|
|
936
|
+
@pytest.mark.asyncio
|
|
937
|
+
async def test_storage_backed_prompt_update_default_version_id(self, temp_dir: str):
|
|
938
|
+
"""Test that StorageBackedPrompt.update_default_version_id works correctly."""
|
|
939
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
940
|
+
|
|
941
|
+
# Create prompt file with multiple versions
|
|
942
|
+
import json
|
|
943
|
+
import os
|
|
944
|
+
|
|
945
|
+
prompt_file = os.path.join(temp_dir, "update_default_test.json")
|
|
946
|
+
with open(prompt_file, "w") as f:
|
|
947
|
+
json.dump(
|
|
948
|
+
{
|
|
949
|
+
"versions": {
|
|
950
|
+
"v1": "Version 1",
|
|
951
|
+
"v2": "Version 2",
|
|
952
|
+
"v3": "Version 3",
|
|
953
|
+
},
|
|
954
|
+
"defaultVersionId": "v1",
|
|
955
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
956
|
+
},
|
|
957
|
+
f,
|
|
958
|
+
)
|
|
959
|
+
|
|
960
|
+
initialize_prompt_storage(temp_dir)
|
|
961
|
+
|
|
962
|
+
prompt = StorageBackedPrompt(id="update_default_test")
|
|
963
|
+
|
|
964
|
+
# Update default version
|
|
965
|
+
result_prompt = prompt.update_default_version_id("v3")
|
|
966
|
+
|
|
967
|
+
# Check that it returns the underlying BasePrompt
|
|
968
|
+
assert isinstance(result_prompt, BasePrompt)
|
|
969
|
+
assert result_prompt.id == "update_default_test"
|
|
970
|
+
assert result_prompt.get_default_version_id() == "v3"
|
|
971
|
+
|
|
972
|
+
# Check that storage was updated
|
|
973
|
+
storage = _FilePromptStorage(temp_dir)
|
|
974
|
+
stored_prompt = storage.get("update_default_test")
|
|
975
|
+
assert stored_prompt.get_default_version_id() == "v3"
|
|
976
|
+
|
|
977
|
+
@pytest.mark.asyncio
|
|
978
|
+
async def test_storage_backed_prompt_exists_in_storage_without_init_raises_error(
|
|
979
|
+
self,
|
|
980
|
+
):
|
|
981
|
+
"""Test exists_in_storage raises error when storage not initialized."""
|
|
982
|
+
from pixie.prompts.storage import StorageBackedPrompt
|
|
983
|
+
import pixie.prompts.storage as storage_module
|
|
984
|
+
|
|
985
|
+
# Ensure storage is not initialized
|
|
986
|
+
storage_module._storage_instance = None
|
|
987
|
+
|
|
988
|
+
prompt = StorageBackedPrompt(id="test")
|
|
989
|
+
|
|
990
|
+
with pytest.raises(
|
|
991
|
+
RuntimeError, match="Prompt storage has not been initialized"
|
|
992
|
+
):
|
|
993
|
+
prompt.exists_in_storage()
|
|
994
|
+
|
|
995
|
+
@pytest.mark.asyncio
|
|
996
|
+
async def test_storage_backed_prompt_actualize_loads_prompt(self, temp_dir: str):
|
|
997
|
+
"""Test that actualize loads the prompt and returns self."""
|
|
998
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
999
|
+
|
|
1000
|
+
# Create prompt file directly
|
|
1001
|
+
import json
|
|
1002
|
+
import os
|
|
1003
|
+
|
|
1004
|
+
prompt_file = os.path.join(temp_dir, "actualize_test.json")
|
|
1005
|
+
with open(prompt_file, "w") as f:
|
|
1006
|
+
json.dump(
|
|
1007
|
+
{
|
|
1008
|
+
"versions": {"v1": "Hello {name}"},
|
|
1009
|
+
"defaultVersionId": "v1",
|
|
1010
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
1011
|
+
},
|
|
1012
|
+
f,
|
|
1013
|
+
)
|
|
1014
|
+
|
|
1015
|
+
initialize_prompt_storage(temp_dir)
|
|
1016
|
+
|
|
1017
|
+
prompt = StorageBackedPrompt(id="actualize_test")
|
|
1018
|
+
assert prompt._prompt is None
|
|
1019
|
+
|
|
1020
|
+
# Call actualize
|
|
1021
|
+
result = prompt.actualize()
|
|
1022
|
+
assert result is prompt
|
|
1023
|
+
assert prompt._prompt is not None
|
|
1024
|
+
|
|
1025
|
+
# Verify it works
|
|
1026
|
+
versions = prompt.get_versions()
|
|
1027
|
+
assert versions == {"v1": "Hello {name}"}
|
|
1028
|
+
|
|
1029
|
+
@pytest.mark.asyncio
|
|
1030
|
+
async def test_storage_backed_prompt_actualize_with_variables_definition(
|
|
1031
|
+
self, temp_dir: str
|
|
1032
|
+
):
|
|
1033
|
+
"""Test actualize with variables_definition performs schema compatibility check."""
|
|
1034
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
1035
|
+
from pixie.prompts.prompt import PromptVariables
|
|
1036
|
+
|
|
1037
|
+
class TestVars(PromptVariables):
|
|
1038
|
+
name: str
|
|
1039
|
+
|
|
1040
|
+
# Create prompt file with compatible schema
|
|
1041
|
+
import json
|
|
1042
|
+
import os
|
|
1043
|
+
|
|
1044
|
+
prompt_file = os.path.join(temp_dir, "actualize_vars_test.json")
|
|
1045
|
+
with open(prompt_file, "w") as f:
|
|
1046
|
+
json.dump(
|
|
1047
|
+
{
|
|
1048
|
+
"versions": {"v1": "Hello {name}"},
|
|
1049
|
+
"defaultVersionId": "v1",
|
|
1050
|
+
"variablesSchema": {
|
|
1051
|
+
"type": "object",
|
|
1052
|
+
"properties": {"name": {"type": "string"}},
|
|
1053
|
+
"required": ["name"],
|
|
1054
|
+
},
|
|
1055
|
+
},
|
|
1056
|
+
f,
|
|
1057
|
+
)
|
|
1058
|
+
|
|
1059
|
+
initialize_prompt_storage(temp_dir)
|
|
1060
|
+
|
|
1061
|
+
prompt = StorageBackedPrompt(
|
|
1062
|
+
id="actualize_vars_test", variables_definition=TestVars
|
|
1063
|
+
)
|
|
1064
|
+
|
|
1065
|
+
# Should succeed - schemas are compatible
|
|
1066
|
+
result = prompt.actualize()
|
|
1067
|
+
assert result is prompt
|
|
1068
|
+
assert prompt._prompt is not None
|
|
1069
|
+
|
|
1070
|
+
@pytest.mark.asyncio
|
|
1071
|
+
async def test_storage_backed_prompt_actualize_incompatible_schema_raises_error(
|
|
1072
|
+
self, temp_dir: str
|
|
1073
|
+
):
|
|
1074
|
+
"""Test actualize raises error when schema is incompatible."""
|
|
1075
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
1076
|
+
from pixie.prompts.prompt import PromptVariables
|
|
1077
|
+
|
|
1078
|
+
class TestVars(PromptVariables):
|
|
1079
|
+
age: int # Different from what's in storage
|
|
1080
|
+
|
|
1081
|
+
# Create prompt file with incompatible schema
|
|
1082
|
+
import json
|
|
1083
|
+
import os
|
|
1084
|
+
|
|
1085
|
+
prompt_file = os.path.join(temp_dir, "incompatible_test.json")
|
|
1086
|
+
with open(prompt_file, "w") as f:
|
|
1087
|
+
json.dump(
|
|
1088
|
+
{
|
|
1089
|
+
"versions": {"v1": "Hello {name}"},
|
|
1090
|
+
"defaultVersionId": "v1",
|
|
1091
|
+
"variablesSchema": {
|
|
1092
|
+
"type": "object",
|
|
1093
|
+
"properties": {"name": {"type": "string"}},
|
|
1094
|
+
"required": ["name"],
|
|
1095
|
+
},
|
|
1096
|
+
},
|
|
1097
|
+
f,
|
|
1098
|
+
)
|
|
1099
|
+
|
|
1100
|
+
initialize_prompt_storage(temp_dir)
|
|
1101
|
+
|
|
1102
|
+
prompt = StorageBackedPrompt(
|
|
1103
|
+
id="incompatible_test", variables_definition=TestVars
|
|
1104
|
+
)
|
|
1105
|
+
|
|
1106
|
+
# Should raise TypeError due to incompatible schema
|
|
1107
|
+
with pytest.raises(
|
|
1108
|
+
TypeError, match="The provided variables_definition is not compatible"
|
|
1109
|
+
):
|
|
1110
|
+
prompt.actualize()
|
|
1111
|
+
|
|
1112
|
+
@pytest.mark.asyncio
|
|
1113
|
+
async def test_storage_backed_prompt_append_version_updates_storage(
|
|
1114
|
+
self, temp_dir: str
|
|
1115
|
+
):
|
|
1116
|
+
"""Test that append_version updates the storage file."""
|
|
1117
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
1118
|
+
|
|
1119
|
+
# Create initial prompt file
|
|
1120
|
+
import json
|
|
1121
|
+
import os
|
|
1122
|
+
|
|
1123
|
+
prompt_file = os.path.join(temp_dir, "storage_update_test.json")
|
|
1124
|
+
with open(prompt_file, "w") as f:
|
|
1125
|
+
json.dump(
|
|
1126
|
+
{
|
|
1127
|
+
"versions": {"v1": "Initial"},
|
|
1128
|
+
"defaultVersionId": "v1",
|
|
1129
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
1130
|
+
},
|
|
1131
|
+
f,
|
|
1132
|
+
)
|
|
1133
|
+
|
|
1134
|
+
initialize_prompt_storage(temp_dir)
|
|
1135
|
+
|
|
1136
|
+
prompt = StorageBackedPrompt(id="storage_update_test")
|
|
1137
|
+
|
|
1138
|
+
# Append version
|
|
1139
|
+
prompt.append_version(version_id="v2", content="Added version")
|
|
1140
|
+
|
|
1141
|
+
# Check file was updated
|
|
1142
|
+
with open(prompt_file, "r") as f:
|
|
1143
|
+
data = json.load(f)
|
|
1144
|
+
|
|
1145
|
+
assert "v2" in data["versions"]
|
|
1146
|
+
assert data["versions"]["v2"] == "Added version"
|
|
1147
|
+
|
|
1148
|
+
@pytest.mark.asyncio
|
|
1149
|
+
async def test_storage_backed_prompt_update_default_updates_storage(
|
|
1150
|
+
self, temp_dir: str
|
|
1151
|
+
):
|
|
1152
|
+
"""Test that update_default_version_id updates the storage file."""
|
|
1153
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
1154
|
+
|
|
1155
|
+
# Create initial prompt file
|
|
1156
|
+
import json
|
|
1157
|
+
import os
|
|
1158
|
+
|
|
1159
|
+
prompt_file = os.path.join(temp_dir, "default_update_test.json")
|
|
1160
|
+
with open(prompt_file, "w") as f:
|
|
1161
|
+
json.dump(
|
|
1162
|
+
{
|
|
1163
|
+
"versions": {"v1": "Version 1", "v2": "Version 2"},
|
|
1164
|
+
"defaultVersionId": "v1",
|
|
1165
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
1166
|
+
},
|
|
1167
|
+
f,
|
|
1168
|
+
)
|
|
1169
|
+
|
|
1170
|
+
initialize_prompt_storage(temp_dir)
|
|
1171
|
+
|
|
1172
|
+
prompt = StorageBackedPrompt(id="default_update_test")
|
|
1173
|
+
|
|
1174
|
+
# Update default
|
|
1175
|
+
prompt.update_default_version_id("v2")
|
|
1176
|
+
|
|
1177
|
+
# Check file was updated
|
|
1178
|
+
with open(prompt_file, "r") as f:
|
|
1179
|
+
data = json.load(f)
|
|
1180
|
+
|
|
1181
|
+
assert data["defaultVersionId"] == "v2"
|
|
1182
|
+
|
|
1183
|
+
@pytest.mark.asyncio
|
|
1184
|
+
async def test_storage_backed_prompt_append_version_raises_for_existing_id(
|
|
1185
|
+
self, temp_dir: str
|
|
1186
|
+
):
|
|
1187
|
+
"""Test that append_version raises error for existing version ID."""
|
|
1188
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
1189
|
+
|
|
1190
|
+
# Create prompt file
|
|
1191
|
+
import json
|
|
1192
|
+
import os
|
|
1193
|
+
|
|
1194
|
+
prompt_file = os.path.join(temp_dir, "duplicate_version_test.json")
|
|
1195
|
+
with open(prompt_file, "w") as f:
|
|
1196
|
+
json.dump(
|
|
1197
|
+
{
|
|
1198
|
+
"versions": {"v1": "Version 1"},
|
|
1199
|
+
"defaultVersionId": "v1",
|
|
1200
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
1201
|
+
},
|
|
1202
|
+
f,
|
|
1203
|
+
)
|
|
1204
|
+
|
|
1205
|
+
initialize_prompt_storage(temp_dir)
|
|
1206
|
+
|
|
1207
|
+
prompt = StorageBackedPrompt(id="duplicate_version_test")
|
|
1208
|
+
|
|
1209
|
+
# Try to append existing version
|
|
1210
|
+
with pytest.raises(ValueError, match="Version ID 'v1' already exists"):
|
|
1211
|
+
prompt.append_version(version_id="v1", content="Duplicate")
|
|
1212
|
+
|
|
1213
|
+
@pytest.mark.asyncio
|
|
1214
|
+
async def test_storage_backed_prompt_update_default_raises_for_nonexistent_id(
|
|
1215
|
+
self, temp_dir: str
|
|
1216
|
+
):
|
|
1217
|
+
"""Test that update_default_version_id raises error for nonexistent version ID."""
|
|
1218
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
1219
|
+
|
|
1220
|
+
# Create prompt file
|
|
1221
|
+
import json
|
|
1222
|
+
import os
|
|
1223
|
+
|
|
1224
|
+
prompt_file = os.path.join(temp_dir, "nonexistent_default_test.json")
|
|
1225
|
+
with open(prompt_file, "w") as f:
|
|
1226
|
+
json.dump(
|
|
1227
|
+
{
|
|
1228
|
+
"versions": {"v1": "Version 1"},
|
|
1229
|
+
"defaultVersionId": "v1",
|
|
1230
|
+
"variablesSchema": {"type": "object", "properties": {}},
|
|
1231
|
+
},
|
|
1232
|
+
f,
|
|
1233
|
+
)
|
|
1234
|
+
|
|
1235
|
+
initialize_prompt_storage(temp_dir)
|
|
1236
|
+
|
|
1237
|
+
prompt = StorageBackedPrompt(id="nonexistent_default_test")
|
|
1238
|
+
|
|
1239
|
+
# Try to update to nonexistent version
|
|
1240
|
+
with pytest.raises(ValueError, match="Version ID 'nonexistent' does not exist"):
|
|
1241
|
+
prompt.update_default_version_id("nonexistent")
|
|
1242
|
+
|
|
1243
|
+
@pytest.mark.asyncio
|
|
1244
|
+
async def test_storage_backed_prompt_append_version_without_init_raises_error(self):
|
|
1245
|
+
"""Test that append_version raises error when storage not initialized."""
|
|
1246
|
+
from pixie.prompts.storage import StorageBackedPrompt
|
|
1247
|
+
import pixie.prompts.storage as storage_module
|
|
1248
|
+
|
|
1249
|
+
# Ensure storage is not initialized
|
|
1250
|
+
storage_module._storage_instance = None
|
|
1251
|
+
|
|
1252
|
+
prompt = StorageBackedPrompt(id="test")
|
|
1253
|
+
|
|
1254
|
+
with pytest.raises(
|
|
1255
|
+
RuntimeError, match="Prompt storage has not been initialized"
|
|
1256
|
+
):
|
|
1257
|
+
prompt.append_version(version_id="v2", content="New version")
|
|
1258
|
+
|
|
1259
|
+
@pytest.mark.asyncio
|
|
1260
|
+
async def test_storage_backed_prompt_update_default_without_init_raises_error(self):
|
|
1261
|
+
"""Test that update_default_version_id raises error when storage not initialized."""
|
|
1262
|
+
from pixie.prompts.storage import StorageBackedPrompt
|
|
1263
|
+
import pixie.prompts.storage as storage_module
|
|
1264
|
+
|
|
1265
|
+
# Ensure storage is not initialized
|
|
1266
|
+
storage_module._storage_instance = None
|
|
1267
|
+
|
|
1268
|
+
prompt = StorageBackedPrompt(id="test")
|
|
1269
|
+
|
|
1270
|
+
with pytest.raises(
|
|
1271
|
+
RuntimeError, match="Prompt storage has not been initialized"
|
|
1272
|
+
):
|
|
1273
|
+
prompt.update_default_version_id("v2")
|
|
1274
|
+
|
|
1275
|
+
@pytest.mark.asyncio
|
|
1276
|
+
async def test_storage_backed_prompt_deletion_during_runtime(self, temp_dir: str):
|
|
1277
|
+
"""Test behavior when a prompt is deleted from storage during runtime."""
|
|
1278
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
1279
|
+
import os
|
|
1280
|
+
|
|
1281
|
+
# Create prompt file
|
|
1282
|
+
prompt_file = os.path.join(temp_dir, "deletion_test.json")
|
|
1283
|
+
with open(prompt_file, "w") as f:
|
|
1284
|
+
json.dump(
|
|
1285
|
+
{
|
|
1286
|
+
"versions": {"v1": "Hello {name}"},
|
|
1287
|
+
"defaultVersionId": "v1",
|
|
1288
|
+
"variablesSchema": {
|
|
1289
|
+
"type": "object",
|
|
1290
|
+
"properties": {"name": {"type": "string"}},
|
|
1291
|
+
},
|
|
1292
|
+
},
|
|
1293
|
+
f,
|
|
1294
|
+
)
|
|
1295
|
+
|
|
1296
|
+
# Initialize storage
|
|
1297
|
+
initialize_prompt_storage(temp_dir)
|
|
1298
|
+
|
|
1299
|
+
prompt = StorageBackedPrompt(id="deletion_test")
|
|
1300
|
+
|
|
1301
|
+
# Delete the prompt file
|
|
1302
|
+
os.remove(prompt_file)
|
|
1303
|
+
|
|
1304
|
+
# Attempt to access the deleted prompt
|
|
1305
|
+
# The prompt is still in storage's in-memory cache, so it should work
|
|
1306
|
+
# but raises TypeError due to schema incompatibility with NoneType default
|
|
1307
|
+
with pytest.raises(TypeError, match="not compatible"):
|
|
1308
|
+
prompt.get_versions()
|
|
1309
|
+
|
|
1310
|
+
|
|
1311
|
+
class TestInitializePromptStorage:
|
|
1312
|
+
"""Tests for initialize_prompt_storage function."""
|
|
1313
|
+
|
|
1314
|
+
@pytest.fixture
|
|
1315
|
+
def temp_dir(self):
|
|
1316
|
+
"""Create a temporary directory for testing."""
|
|
1317
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1318
|
+
yield tmpdir
|
|
1319
|
+
|
|
1320
|
+
@pytest.fixture(autouse=True)
|
|
1321
|
+
def reset_storage_instance(self):
|
|
1322
|
+
"""Reset the global storage instance before each test."""
|
|
1323
|
+
import pixie.prompts.storage as storage_module
|
|
1324
|
+
|
|
1325
|
+
storage_module._storage_instance = None
|
|
1326
|
+
yield
|
|
1327
|
+
storage_module._storage_instance = None
|
|
1328
|
+
|
|
1329
|
+
def test_initialize_prompt_storage_once(self, temp_dir: str):
|
|
1330
|
+
"""Test that initialize_prompt_storage can only be called once."""
|
|
1331
|
+
from pixie.prompts.storage import initialize_prompt_storage
|
|
1332
|
+
|
|
1333
|
+
initialize_prompt_storage(temp_dir)
|
|
1334
|
+
|
|
1335
|
+
# Should raise error on second call
|
|
1336
|
+
with pytest.raises(
|
|
1337
|
+
RuntimeError, match="Prompt storage has already been initialized"
|
|
1338
|
+
):
|
|
1339
|
+
initialize_prompt_storage(temp_dir)
|
|
1340
|
+
|
|
1341
|
+
def test_initialize_creates_storage(self, temp_dir: str):
|
|
1342
|
+
"""Test that initialize_prompt_storage creates a FilePromptStorage instance."""
|
|
1343
|
+
from pixie.prompts.storage import initialize_prompt_storage
|
|
1344
|
+
import pixie.prompts.storage as storage_module
|
|
1345
|
+
|
|
1346
|
+
initialize_prompt_storage(temp_dir)
|
|
1347
|
+
|
|
1348
|
+
assert storage_module._storage_instance is not None
|
|
1349
|
+
assert isinstance(storage_module._storage_instance, _FilePromptStorage)
|
|
1350
|
+
|
|
1351
|
+
@pytest.mark.asyncio
|
|
1352
|
+
async def test_storage_backed_prompt_append_version_schema_incompatibility(
|
|
1353
|
+
self, temp_dir: str
|
|
1354
|
+
):
|
|
1355
|
+
"""Test that appending a version with incompatible schema raises an error."""
|
|
1356
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
1357
|
+
from pixie.prompts.prompt import PromptVariables
|
|
1358
|
+
|
|
1359
|
+
class OriginalVars(PromptVariables):
|
|
1360
|
+
name: str
|
|
1361
|
+
|
|
1362
|
+
class IncompatibleVars(PromptVariables):
|
|
1363
|
+
age: int
|
|
1364
|
+
|
|
1365
|
+
# Create prompt file directly
|
|
1366
|
+
import json
|
|
1367
|
+
import os
|
|
1368
|
+
|
|
1369
|
+
prompt_file = os.path.join(temp_dir, "schema_test.json")
|
|
1370
|
+
with open(prompt_file, "w") as f:
|
|
1371
|
+
json.dump(
|
|
1372
|
+
{
|
|
1373
|
+
"versions": {"v1": "Hello {name}"},
|
|
1374
|
+
"defaultVersionId": "v1",
|
|
1375
|
+
"variablesSchema": {
|
|
1376
|
+
"type": "object",
|
|
1377
|
+
"properties": {"name": {"type": "string"}},
|
|
1378
|
+
},
|
|
1379
|
+
},
|
|
1380
|
+
f,
|
|
1381
|
+
)
|
|
1382
|
+
|
|
1383
|
+
# Initialize storage
|
|
1384
|
+
initialize_prompt_storage(temp_dir)
|
|
1385
|
+
|
|
1386
|
+
# Create StorageBackedPrompt with original schema
|
|
1387
|
+
prompt = StorageBackedPrompt(
|
|
1388
|
+
id="schema_test", variables_definition=OriginalVars
|
|
1389
|
+
)
|
|
1390
|
+
|
|
1391
|
+
# Attempt to append a version with incompatible schema
|
|
1392
|
+
with pytest.raises(TypeError, match="Original schema must be a subschema"):
|
|
1393
|
+
prompt.append_version(version_id="v2", content="Hi {age}")
|
|
1394
|
+
|
|
1395
|
+
@pytest.mark.asyncio
|
|
1396
|
+
async def test_storage_backed_prompt_concurrent_append_version(self, temp_dir: str):
|
|
1397
|
+
"""Test concurrent calls to append_version to ensure thread safety."""
|
|
1398
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
1399
|
+
import asyncio
|
|
1400
|
+
|
|
1401
|
+
# Initialize storage
|
|
1402
|
+
initialize_prompt_storage(temp_dir)
|
|
1403
|
+
|
|
1404
|
+
prompt = StorageBackedPrompt(id="concurrent_test")
|
|
1405
|
+
|
|
1406
|
+
async def append_version(version_id, content):
|
|
1407
|
+
prompt.append_version(version_id=version_id, content=content)
|
|
1408
|
+
|
|
1409
|
+
# Run concurrent appends
|
|
1410
|
+
await asyncio.gather(
|
|
1411
|
+
append_version("v1", "Hello {name}"),
|
|
1412
|
+
append_version("v2", "Hi {name}"),
|
|
1413
|
+
)
|
|
1414
|
+
|
|
1415
|
+
# Check that both versions exist
|
|
1416
|
+
versions = prompt.get_versions()
|
|
1417
|
+
assert "v1" in versions
|
|
1418
|
+
assert "v2" in versions
|
|
1419
|
+
assert versions["v1"] == "Hello {name}"
|
|
1420
|
+
assert versions["v2"] == "Hi {name}"
|
|
1421
|
+
|
|
1422
|
+
@pytest.mark.asyncio
|
|
1423
|
+
async def test_storage_backed_prompt_append_version_invalid_version_id(
|
|
1424
|
+
self, temp_dir: str
|
|
1425
|
+
):
|
|
1426
|
+
"""Test that appending a version with an empty version ID works (no validation)."""
|
|
1427
|
+
from pixie.prompts.storage import initialize_prompt_storage, StorageBackedPrompt
|
|
1428
|
+
|
|
1429
|
+
# Initialize storage
|
|
1430
|
+
initialize_prompt_storage(temp_dir)
|
|
1431
|
+
|
|
1432
|
+
prompt = StorageBackedPrompt(id="invalid_version_test")
|
|
1433
|
+
|
|
1434
|
+
# Empty version ID is actually allowed - no validation in place
|
|
1435
|
+
result = prompt.append_version(version_id="", content="Hello {name}")
|
|
1436
|
+
assert result is not None
|
|
1437
|
+
versions = result.get_versions()
|
|
1438
|
+
assert "" in versions
|
|
1439
|
+
|
|
1440
|
+
@pytest.mark.asyncio
|
|
1441
|
+
async def test_storage_backed_prompt_corrupted_storage(self, temp_dir: str):
|
|
1442
|
+
"""Test behavior when storage files are corrupted."""
|
|
1443
|
+
from pixie.prompts.storage import initialize_prompt_storage
|
|
1444
|
+
import os
|
|
1445
|
+
|
|
1446
|
+
# Create corrupted prompt file
|
|
1447
|
+
corrupted_file = os.path.join(temp_dir, "corrupted_test.json")
|
|
1448
|
+
with open(corrupted_file, "w") as f:
|
|
1449
|
+
f.write("{invalid_json}")
|
|
1450
|
+
|
|
1451
|
+
# Initialize storage - should raise JSONDecodeError when loading corrupted file
|
|
1452
|
+
with pytest.raises(json.JSONDecodeError):
|
|
1453
|
+
initialize_prompt_storage(temp_dir)
|