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.
@@ -0,0 +1,1321 @@
1
+ """Comprehensive unit tests for pixie.prompts.prompt module."""
2
+
3
+ import os
4
+ import json
5
+ import pytest
6
+ import tempfile
7
+ from types import NoneType
8
+ from jinja2 import UndefinedError
9
+
10
+ from pixie.prompts.storage import _FilePromptStorage
11
+ from pixie.prompts.prompt import (
12
+ DEFAULT_VERSION_ID,
13
+ BasePrompt,
14
+ PromptVariables,
15
+ BaseUntypedPrompt,
16
+ OutdatedPrompt,
17
+ _prompt_registry, # Import the registry explicitly
18
+ _compiled_prompt_registry,
19
+ )
20
+
21
+
22
+ class SamplePromptVariables(PromptVariables):
23
+ """Sample subclass of PromptVariables for testing."""
24
+
25
+ name: str
26
+ age: int
27
+ city: str = "Unknown"
28
+
29
+
30
+ class AnotherPromptVariables(PromptVariables):
31
+ """Another sample subclass with different fields."""
32
+
33
+ greeting: str
34
+ topic: str
35
+
36
+
37
+ class NestedPromptVariables(PromptVariables):
38
+ """Sample with nested model."""
39
+
40
+ user: SamplePromptVariables
41
+ message: str
42
+
43
+
44
+ class TestPromptInitialization:
45
+ """Tests for Prompt initialization."""
46
+
47
+ def test_init_with_string_versions(self):
48
+ """Test that a string version is converted to a dict with 'default' key."""
49
+ prompt = BasePrompt(versions="Hello, world!")
50
+
51
+ assert isinstance(prompt._versions, dict)
52
+ assert DEFAULT_VERSION_ID in prompt._versions
53
+ assert prompt._versions[DEFAULT_VERSION_ID] == "Hello, world!"
54
+
55
+ @pytest.mark.asyncio
56
+ async def test_init_with_dict_versions(self):
57
+ """Test initialization with a dictionary of versions."""
58
+ versions = {"v1": "Version 1", "v2": "Version 2"}
59
+ prompt = BasePrompt(versions=versions)
60
+
61
+ assert prompt._versions == versions
62
+ assert set(prompt._versions.keys()) == {"v1", "v2"}
63
+
64
+ def test_init_with_dict_versions_creates_copy(self):
65
+ """Test that the versions dict is deep copied to prevent external mutations."""
66
+ original_versions = {"v1": "Version 1"}
67
+ prompt = BasePrompt(versions=original_versions)
68
+
69
+ # Modify the original dict
70
+ original_versions["v1"] = "Modified"
71
+ original_versions["v2"] = "New version"
72
+
73
+ # Prompt should still have the original value
74
+ assert prompt._versions["v1"] == "Version 1"
75
+ assert "v2" not in prompt._versions
76
+
77
+ @pytest.mark.asyncio
78
+ async def test_init_with_explicit_default_version(self):
79
+ """Test setting an explicit default version."""
80
+ versions = {"v1": "Version 1", "v2": "Version 2", "v3": "Version 3"}
81
+ prompt = BasePrompt(versions=versions, default_version_id="v2")
82
+
83
+ assert prompt.get_default_version_id() == "v2"
84
+
85
+ @pytest.mark.asyncio
86
+ async def test_init_default_version_is_first_key_when_not_specified(self):
87
+ """Test that default version is the first key when not explicitly set."""
88
+ # Note: dict order is preserved in Python 3.7+
89
+ versions = {"first": "First version", "second": "Second version"}
90
+ prompt = BasePrompt(versions=versions)
91
+
92
+ assert prompt.get_default_version_id() == "first"
93
+
94
+ @pytest.mark.asyncio
95
+ async def test_init_with_string_and_default_version(self):
96
+ """Test that default_version_id works with string versions."""
97
+ prompt = BasePrompt(versions="Hello!", default_version_id="default")
98
+
99
+ assert prompt.get_default_version_id() == "default"
100
+
101
+ def test_init_with_variables_definition(self):
102
+ """Test initialization with variable definitions."""
103
+ prompt = BasePrompt(
104
+ versions="Hello, {name}!",
105
+ variables_definition=SamplePromptVariables,
106
+ )
107
+
108
+ assert prompt._variables_definition == SamplePromptVariables
109
+
110
+ def test_init_with_none_variables_definition(self):
111
+ """Test initialization with NoneType variable definitions (default)."""
112
+
113
+ prompt = BasePrompt(versions="Hello!")
114
+
115
+ assert prompt._variables_definition == NoneType
116
+
117
+
118
+ class TestPromptProperties:
119
+ """Tests for Prompt properties."""
120
+
121
+ @pytest.mark.asyncio
122
+ async def test_version_ids_property(self):
123
+ """Test the version_ids property returns all version keys."""
124
+ versions = {"v1": "Version 1", "v2": "Version 2", "v3": "Version 3"}
125
+ prompt = BasePrompt(versions=versions)
126
+
127
+ version_ids = set(prompt._versions.keys())
128
+
129
+ assert isinstance(version_ids, set)
130
+ assert version_ids == {"v1", "v2", "v3"}
131
+
132
+ @pytest.mark.asyncio
133
+ async def test_version_ids_with_single_version(self):
134
+ """Test version_ids with a single version."""
135
+ prompt = BasePrompt(versions="Single version")
136
+
137
+ assert set(prompt._versions.keys()) == {DEFAULT_VERSION_ID}
138
+
139
+ @pytest.mark.asyncio
140
+ async def test_default_version_id_property(self):
141
+ """Test the default_version_id property."""
142
+ versions = {"v1": "Version 1", "v2": "Version 2"}
143
+ prompt = BasePrompt(versions=versions, default_version_id="v2")
144
+
145
+ assert prompt.get_default_version_id() == "v2"
146
+
147
+ @pytest.mark.asyncio
148
+ async def test_default_version_id_property_with_string_init(self):
149
+ """Test default_version_id when initialized with string."""
150
+ prompt = BasePrompt(versions="Test prompt")
151
+
152
+ assert prompt.get_default_version_id() == DEFAULT_VERSION_ID
153
+
154
+
155
+ class TestPromptCompileWithoutVariables:
156
+ """Tests for Prompt.compile() without variables (NoneType case)."""
157
+
158
+ def test_compile_without_variables_default_version(self):
159
+ """Test compiling a prompt without variables using default version."""
160
+ prompt = BasePrompt(versions="Hello, world!")
161
+
162
+ result = prompt.compile()
163
+
164
+ assert result == "Hello, world!"
165
+
166
+ def test_compile_without_variables_specific_version(self):
167
+ """Test compiling a prompt without variables using a specific version."""
168
+ versions = {
169
+ "formal": "Good day, esteemed user.",
170
+ "casual": "Hey there!",
171
+ "excited": "Hello!!!",
172
+ }
173
+ prompt = BasePrompt(versions=versions, default_version_id="casual")
174
+
175
+ result_formal = prompt.compile(version_id="formal")
176
+ result_casual = prompt.compile(version_id="casual")
177
+ result_excited = prompt.compile(version_id="excited")
178
+
179
+ assert result_formal == "Good day, esteemed user."
180
+ assert result_casual == "Hey there!"
181
+ assert result_excited == "Hello!!!"
182
+
183
+ def test_compile_without_variables_uses_default_when_no_version_specified(self):
184
+ """Test that compile uses default version when version_id is None."""
185
+ versions = {"v1": "Version 1", "v2": "Version 2"}
186
+ prompt = BasePrompt(versions=versions, default_version_id="v2")
187
+
188
+ result = prompt.compile()
189
+
190
+ assert result == "Version 2"
191
+
192
+ def test_compile_plain_text_no_formatting(self):
193
+ """Test compiling plain text without any formatting placeholders."""
194
+ text = "This is a plain text prompt with no variables."
195
+ prompt = BasePrompt(versions=text)
196
+
197
+ result = prompt.compile()
198
+
199
+ assert result == text
200
+
201
+
202
+ class TestPromptCompileWithVariables:
203
+ """Tests for Prompt.compile() with variables."""
204
+
205
+ def test_compile_with_variables(self):
206
+ """Test compiling a prompt with variable substitution."""
207
+ template = "Hello, {{name}}! You are {{age}} years old."
208
+ prompt = BasePrompt(
209
+ versions=template,
210
+ variables_definition=SamplePromptVariables,
211
+ )
212
+
213
+ variables = SamplePromptVariables(name="Alice", age=30)
214
+ result = prompt.compile(variables)
215
+
216
+ assert result == "Hello, Alice! You are 30 years old."
217
+
218
+ def test_compile_with_variables_and_default_values(self):
219
+ """Test that default values in Pydantic model work correctly."""
220
+ template = "Hello, {{name}} from {{city}}!"
221
+ prompt = BasePrompt(
222
+ versions=template,
223
+ variables_definition=SamplePromptVariables,
224
+ )
225
+
226
+ variables = SamplePromptVariables(name="Bob", age=25)
227
+ result = prompt.compile(variables)
228
+
229
+ assert result == "Hello, Bob from Unknown!"
230
+
231
+ def test_compile_with_variables_specific_version(self):
232
+ """Test compiling with variables using a specific version."""
233
+ versions = {
234
+ "greeting": "Hello, {{name}}!",
235
+ "farewell": "Goodbye, {{name}}!",
236
+ "question": "How are you, {{name}}?",
237
+ }
238
+ prompt = BasePrompt(
239
+ versions=versions,
240
+ variables_definition=SamplePromptVariables,
241
+ default_version_id="greeting",
242
+ )
243
+
244
+ variables = SamplePromptVariables(name="Charlie", age=35)
245
+
246
+ greeting = prompt.compile(variables, version_id="greeting")
247
+ farewell = prompt.compile(variables, version_id="farewell")
248
+ question = prompt.compile(variables, version_id="question")
249
+
250
+ assert greeting == "Hello, Charlie!"
251
+ assert farewell == "Goodbye, Charlie!"
252
+ assert question == "How are you, Charlie?"
253
+
254
+ def test_compile_with_multiple_variables(self):
255
+ """Test compiling with multiple variable substitutions."""
256
+ template = "{{greeting}}, {{topic}} is fascinating!"
257
+ prompt = BasePrompt(
258
+ versions=template,
259
+ variables_definition=AnotherPromptVariables,
260
+ )
261
+
262
+ variables = AnotherPromptVariables(greeting="Hello", topic="Python")
263
+ result = prompt.compile(variables)
264
+
265
+ assert result == "Hello, Python is fascinating!"
266
+
267
+ def test_compile_with_variables_complex_template(self):
268
+ """Test compiling with a more complex template."""
269
+ template = """
270
+ Name: {{name}}
271
+ Age: {{age}}
272
+ City: {{city}}
273
+ Status: Active
274
+ """
275
+ prompt = BasePrompt(
276
+ versions=template,
277
+ variables_definition=SamplePromptVariables,
278
+ )
279
+
280
+ variables = SamplePromptVariables(name="Diana", age=28, city="Paris")
281
+ result = prompt.compile(variables)
282
+
283
+ expected = """
284
+ Name: Diana
285
+ Age: 28
286
+ City: Paris
287
+ Status: Active"""
288
+ assert result == expected
289
+
290
+ def test_compile_with_variables_uses_default_version(self):
291
+ """Test that compile with variables uses default version when not specified."""
292
+ versions = {
293
+ "v1": "Version 1: {{name}}",
294
+ "v2": "Version 2: {{name}}",
295
+ }
296
+ prompt = BasePrompt(
297
+ versions=versions,
298
+ variables_definition=SamplePromptVariables,
299
+ default_version_id="v2",
300
+ )
301
+
302
+ variables = SamplePromptVariables(name="Eve", age=40)
303
+ result = prompt.compile(variables)
304
+
305
+ assert result == "Version 2: Eve"
306
+
307
+ def test_compile_with_nested_variables(self):
308
+ """Test compiling with nested property access."""
309
+ template = (
310
+ "Hello {{user.name}}, you are {{user.age}} years old. Message: {{message}}"
311
+ )
312
+ prompt = BasePrompt(
313
+ versions=template,
314
+ variables_definition=NestedPromptVariables,
315
+ )
316
+
317
+ variables = NestedPromptVariables(
318
+ user=SamplePromptVariables(name="Alice", age=30, city="NYC"),
319
+ message="Welcome!",
320
+ )
321
+ result = prompt.compile(variables)
322
+
323
+ assert result == "Hello Alice, you are 30 years old. Message: Welcome!"
324
+
325
+ def test_compile_variables_required_when_definitions_exist(self):
326
+ """Test that ValueError is raised when variables are required but not provided."""
327
+ prompt = BasePrompt(
328
+ versions="Hello, {name}!",
329
+ variables_definition=SamplePromptVariables,
330
+ )
331
+
332
+ with pytest.raises(ValueError):
333
+ prompt.compile() # type: ignore[call-arg]
334
+
335
+ def test_compile_variables_required_with_none_passed(self):
336
+ """Test that ValueError is raised when None is explicitly passed."""
337
+ prompt = BasePrompt(
338
+ versions="Hello, {name}!",
339
+ variables_definition=SamplePromptVariables,
340
+ )
341
+
342
+ with pytest.raises(ValueError):
343
+ prompt.compile(None) # type: ignore[arg-type]
344
+
345
+
346
+ class TestPromptEdgeCases:
347
+ """Tests for edge cases and special scenarios."""
348
+
349
+ def test_empty_string_version(self):
350
+ """Test with an empty string raises ValueError."""
351
+ # Empty string is falsy, so it should fail validation
352
+ with pytest.raises(ValueError, match="No versions provided"):
353
+ BasePrompt(versions="")
354
+
355
+ def test_empty_dict_versions(self):
356
+ """Test with an empty dict raises ValueError (no versions available)."""
357
+ # This creates a prompt with no versions, which fails validation
358
+ with pytest.raises(ValueError, match="No versions provided"):
359
+ BasePrompt(versions={})
360
+
361
+ def test_version_with_special_characters(self):
362
+ """Test version content with special characters."""
363
+ special_text = "Hello! @#$%^&*() {{name}} [brackets] 'quotes' \"double\""
364
+ prompt = BasePrompt(
365
+ versions=special_text,
366
+ variables_definition=SamplePromptVariables,
367
+ )
368
+
369
+ variables = SamplePromptVariables(name="Test", age=20)
370
+ result = prompt.compile(variables)
371
+
372
+ assert result == "Hello! @#$%^&*() Test [brackets] 'quotes' \"double\""
373
+
374
+ def test_version_with_curly_braces_not_variables(self):
375
+ """Test that literal curly braces (doubled) are preserved."""
376
+ template = "This {is} not {{name}} a variable"
377
+ prompt = BasePrompt(
378
+ versions=template,
379
+ variables_definition=SamplePromptVariables,
380
+ )
381
+
382
+ variables = SamplePromptVariables(name="formatted", age=10)
383
+ result = prompt.compile(variables)
384
+
385
+ # Python's .format() handles {{}} as escaped braces
386
+ assert result == "This {is} not formatted a variable"
387
+
388
+ def test_unicode_content(self):
389
+ """Test with Unicode characters in version content."""
390
+ unicode_text = "Hello, {{name}}! 你好 🎉 Привет"
391
+ prompt = BasePrompt(
392
+ versions=unicode_text,
393
+ variables_definition=SamplePromptVariables,
394
+ )
395
+
396
+ variables = SamplePromptVariables(name="World", age=1)
397
+ result = prompt.compile(variables)
398
+
399
+ assert result == "Hello, World! 你好 🎉 Привет"
400
+
401
+ def test_multiline_template(self):
402
+ """Test with a multiline template."""
403
+ template = """Line 1: {{name}}
404
+ Line 2: {{age}}
405
+ Line 3: {{city}}"""
406
+ prompt = BasePrompt(
407
+ versions=template,
408
+ variables_definition=SamplePromptVariables,
409
+ )
410
+
411
+ variables = SamplePromptVariables(name="Frank", age=50, city="London")
412
+ result = prompt.compile(variables)
413
+
414
+ expected = """Line 1: Frank
415
+ Line 2: 50
416
+ Line 3: London"""
417
+ assert result == expected
418
+
419
+ def test_version_id_lookup_key_error_propagates(self):
420
+ """Test that KeyError is raised when version_id doesn't exist."""
421
+ prompt = BasePrompt(versions={"v1": "Version 1"})
422
+
423
+ with pytest.raises(KeyError):
424
+ prompt.compile(version_id="nonexistent")
425
+
426
+ def test_missing_variable_in_template_raises_key_error(self):
427
+ """Test that KeyError is raised when template variable is not in model."""
428
+ template = "Hello, {{name}} and {{missing_var}}!"
429
+ prompt = BasePrompt(
430
+ versions=template,
431
+ variables_definition=SamplePromptVariables,
432
+ )
433
+
434
+ variables = SamplePromptVariables(name="Test", age=25)
435
+
436
+ with pytest.raises(UndefinedError):
437
+ prompt.compile(variables)
438
+
439
+ def test_extra_variables_in_model_dont_affect_compile(self):
440
+ """Test that extra variables in the model that aren't in template are ignored."""
441
+ template = "Hello, {{name}}!"
442
+ prompt = BasePrompt(
443
+ versions=template,
444
+ variables_definition=SamplePromptVariables,
445
+ )
446
+
447
+ # Model has age and city, but template only uses name
448
+ variables = SamplePromptVariables(name="Grace", age=60, city="Tokyo")
449
+ result = prompt.compile(variables)
450
+
451
+ assert result == "Hello, Grace!"
452
+
453
+ def test_numeric_values_in_template(self):
454
+ """Test that numeric values are properly converted to strings."""
455
+ template = "Count: {{age}}, Double: {{age}}"
456
+ prompt = BasePrompt(
457
+ versions=template,
458
+ variables_definition=SamplePromptVariables,
459
+ )
460
+
461
+ variables = SamplePromptVariables(name="Test", age=42)
462
+ result = prompt.compile(variables)
463
+
464
+ assert result == "Count: 42, Double: 42"
465
+
466
+
467
+ class TestPromptTypeAnnotations:
468
+ """Tests related to type annotations and generic behavior."""
469
+
470
+ def test_prompt_with_nonetype_generic(self):
471
+ """Test Prompt with NoneType explicitly."""
472
+
473
+ prompt: BasePrompt[NoneType] = BasePrompt(versions="No variables here")
474
+
475
+ result = prompt.compile()
476
+
477
+ assert result == "No variables here"
478
+
479
+ def test_prompt_with_custom_type_generic(self):
480
+ """Test Prompt with custom PromptVariables type."""
481
+ prompt: BasePrompt[SamplePromptVariables] = BasePrompt(
482
+ versions="Name: {{name}}",
483
+ variables_definition=SamplePromptVariables,
484
+ )
485
+
486
+ variables = SamplePromptVariables(name="Type Test", age=99)
487
+ result = prompt.compile(variables)
488
+
489
+ assert result == "Name: Type Test"
490
+
491
+
492
+ class TestPromptIntegration:
493
+ """Integration tests combining multiple features."""
494
+
495
+ def test_multiple_versions_with_variables(self):
496
+ """Test using multiple versions with the same variable definitions."""
497
+ versions = {
498
+ "short": "{{greeting}}!",
499
+ "medium": "{{greeting}}, let's talk about {{topic}}.",
500
+ "long": "{{greeting}}! Today we'll discuss {{topic}} in detail.",
501
+ }
502
+ prompt = BasePrompt(
503
+ versions=versions,
504
+ variables_definition=AnotherPromptVariables,
505
+ default_version_id="medium",
506
+ )
507
+
508
+ variables = AnotherPromptVariables(greeting="Hi", topic="AI")
509
+
510
+ short = prompt.compile(variables, version_id="short")
511
+ medium = prompt.compile(variables) # Uses default
512
+ long = prompt.compile(variables, version_id="long")
513
+
514
+ assert short == "Hi!"
515
+ assert medium == "Hi, let's talk about AI."
516
+ assert long == "Hi! Today we'll discuss AI in detail."
517
+
518
+ @pytest.mark.asyncio
519
+ async def test_switching_versions_maintains_state(self):
520
+ """Test that switching versions doesn't affect internal state."""
521
+ versions = {"v1": "Version 1", "v2": "Version 2"}
522
+ prompt = BasePrompt(versions=versions, default_version_id="v1")
523
+
524
+ result1 = prompt.compile(version_id="v2")
525
+ result2 = prompt.compile() # Should still use v1 as default
526
+
527
+ assert result1 == "Version 2"
528
+ assert result2 == "Version 1"
529
+ assert prompt.get_default_version_id() == "v1"
530
+
531
+ def test_same_prompt_compiled_multiple_times(self):
532
+ """Test that compiling the same prompt multiple times produces consistent results."""
533
+ template = "Hello, {{name}}!"
534
+ prompt = BasePrompt(
535
+ versions=template,
536
+ variables_definition=SamplePromptVariables,
537
+ )
538
+
539
+ variables = SamplePromptVariables(name="Harry", age=45)
540
+
541
+ results = [prompt.compile(variables) for _ in range(5)]
542
+
543
+ assert all(r == "Hello, Harry!" for r in results)
544
+
545
+ def test_different_variable_instances_same_values(self):
546
+ """Test that different variable instances with same values produce same output."""
547
+ prompt = BasePrompt(
548
+ versions="{{name}} is {{age}}",
549
+ variables_definition=SamplePromptVariables,
550
+ )
551
+
552
+ vars1 = SamplePromptVariables(name="Ivy", age=33)
553
+ vars2 = SamplePromptVariables(name="Ivy", age=33)
554
+
555
+ result1 = prompt.compile(vars1)
556
+ result2 = prompt.compile(vars2)
557
+
558
+ assert result1 == result2
559
+ assert result1 == "Ivy is 33"
560
+
561
+
562
+ class TestPromptUpdateAndOutdated:
563
+ """Tests for Prompt.update() and OutdatedPrompt behavior."""
564
+
565
+ @pytest.mark.asyncio
566
+ async def test_prompt_update_returns_outdated_prompt(self):
567
+ """Test that Prompt.update() returns an OutdatedPrompt."""
568
+ prompt = BasePrompt(versions={"v1": "Original"})
569
+ current, outdated = prompt._update(versions={"v1": "Updated"})
570
+
571
+ assert isinstance(outdated, OutdatedPrompt)
572
+ assert outdated.id == prompt.id
573
+ assert outdated.get_versions() == {"v1": "Original"}
574
+
575
+ assert current == prompt # Current prompt remains the same instance
576
+ assert current.get_versions() == {"v1": "Updated"}
577
+
578
+ @pytest.mark.asyncio
579
+ async def test_prompt_update_modifies_prompt_in_place(self):
580
+ """Test that Prompt.update() modifies the prompt object in place."""
581
+ prompt = BasePrompt(versions={"v1": "Original"}, default_version_id="v1")
582
+
583
+ prompt._update(
584
+ versions={"v1": "Updated", "v2": "New version"}, default_version_id="v2"
585
+ )
586
+
587
+ versions = prompt.get_versions()
588
+ assert versions["v1"] == "Updated"
589
+ assert versions["v2"] == "New version"
590
+ assert prompt.get_default_version_id() == "v2"
591
+
592
+ @pytest.mark.asyncio
593
+ async def test_prompt_remains_usable_after_update(self):
594
+ """Test that Prompt remains usable after update."""
595
+ prompt = BasePrompt(versions={"v1": "Original {name}"})
596
+
597
+ # Compile before update
598
+ result_before = prompt.compile()
599
+
600
+ # Update
601
+ prompt._update(versions={"v1": "Updated {name}"})
602
+
603
+ # Compile after update
604
+ result_after = prompt.compile()
605
+
606
+ assert result_before == "Original {name}"
607
+ assert result_after == "Updated {name}"
608
+
609
+ def test_outdated_prompt_compile_raises_error(self):
610
+ """Test that OutdatedPrompt.compile() raises ValueError."""
611
+ outdated = OutdatedPrompt(
612
+ versions={"v1": "Test"},
613
+ default_version_id="v1",
614
+ id="test_id",
615
+ variables_definition=NoneType,
616
+ )
617
+
618
+ with pytest.raises(ValueError, match="This prompt is outdated"):
619
+ outdated.compile()
620
+
621
+ @pytest.mark.asyncio
622
+ async def test_outdated_prompt_update_does_nothing(self):
623
+ """Test that OutdatedPrompt.update() returns self without changes."""
624
+ outdated = OutdatedPrompt(
625
+ versions={"v1": "Original"},
626
+ default_version_id="v1",
627
+ id="test_id",
628
+ variables_definition=NoneType,
629
+ )
630
+
631
+ with pytest.raises(ValueError):
632
+ outdated._update(versions={"v1": "Updated"})
633
+
634
+ @pytest.mark.asyncio
635
+ async def test_compiled_prompts_become_outdated_on_update(self):
636
+ """Test that compiled prompts reference OutdatedPrompt after prompt update."""
637
+ prompt = BasePrompt(
638
+ versions={"v1": "Version {name}"},
639
+ variables_definition=SamplePromptVariables,
640
+ )
641
+ variables = SamplePromptVariables(name="Test", age=25)
642
+
643
+ # Compile a prompt
644
+ compiled_result = prompt.compile(variables)
645
+
646
+ # Find the compiled prompt in registry
647
+ compiled_prompt = None
648
+ for cp in _compiled_prompt_registry.values():
649
+ if cp.value == compiled_result:
650
+ compiled_prompt = cp
651
+ break
652
+
653
+ assert compiled_prompt is not None
654
+ assert isinstance(compiled_prompt.prompt, BasePrompt)
655
+
656
+ # Update the prompt
657
+ prompt._update(versions={"v1": "Updated {name}"})
658
+
659
+ # Check that the same compiled prompt now references OutdatedPrompt
660
+ updated_compiled = None
661
+ for cp in _compiled_prompt_registry.values():
662
+ if cp.value == compiled_result:
663
+ updated_compiled = cp
664
+ break
665
+
666
+ assert updated_compiled is not None
667
+ assert isinstance(updated_compiled.prompt, OutdatedPrompt)
668
+ versions = updated_compiled.prompt.get_versions()
669
+ assert versions["v1"] == "Version {name}"
670
+
671
+ @pytest.mark.asyncio
672
+ async def test_outdated_compiled_prompt_cannot_compile(self):
673
+ """Test that trying to compile an outdated compiled prompt raises error."""
674
+ prompt = BasePrompt(
675
+ versions={"v1": "Version {name}"},
676
+ variables_definition=SamplePromptVariables,
677
+ )
678
+ variables = SamplePromptVariables(name="Test", age=25)
679
+
680
+ # Compile a prompt
681
+ compiled_result = prompt.compile(variables)
682
+
683
+ # Update the prompt, making compiled prompt outdated
684
+ prompt._update(versions={"v1": "Updated {name}"})
685
+
686
+ # Find the outdated compiled prompt
687
+ outdated_cp = None
688
+ for cp in _compiled_prompt_registry.values():
689
+ if cp.value == compiled_result:
690
+ outdated_cp = cp
691
+ break
692
+
693
+ assert outdated_cp is not None
694
+ assert isinstance(outdated_cp.prompt, OutdatedPrompt)
695
+
696
+ with pytest.raises(ValueError, match="This prompt is outdated"):
697
+ outdated_cp.prompt.compile(variables)
698
+
699
+
700
+ @pytest.mark.asyncio
701
+ class TestBasePromptNewMethods:
702
+ """Tests for newly added methods on BasePrompt: append_version and update_default_version_id."""
703
+
704
+ @pytest.fixture(autouse=True)
705
+ def clear_compiled_registry(self):
706
+ """Clear the compiled prompt registry before each test."""
707
+ _compiled_prompt_registry.clear()
708
+
709
+ async def test_append_version_adds_new_version(self):
710
+ """Test that append_version adds a new version to the prompt."""
711
+ prompt = BasePrompt(versions={"v1": "Version 1"})
712
+
713
+ prompt.append_version(version_id="v2", content="Version 2")
714
+
715
+ versions = prompt.get_versions()
716
+ assert "v2" in versions
717
+ assert versions["v2"] == "Version 2"
718
+ assert versions["v1"] == "Version 1" # Original version still exists
719
+
720
+ async def test_append_version_sets_as_default_when_requested(self):
721
+ """Test that append_version can set the new version as default."""
722
+ prompt = BasePrompt(versions={"v1": "Version 1"}, default_version_id="v1")
723
+
724
+ prompt.append_version(version_id="v2", content="Version 2", set_as_default=True)
725
+
726
+ assert prompt.get_default_version_id() == "v2"
727
+
728
+ async def test_append_version_keeps_existing_default_when_not_requested(self):
729
+ """Test that append_version keeps existing default when set_as_default=False."""
730
+ prompt = BasePrompt(versions={"v1": "Version 1"}, default_version_id="v1")
731
+
732
+ prompt.append_version(
733
+ version_id="v2", content="Version 2", set_as_default=False
734
+ )
735
+
736
+ assert prompt.get_default_version_id() == "v1"
737
+
738
+ async def test_append_version_raises_error_for_existing_version_id(self):
739
+ """Test that append_version raises ValueError for existing version ID."""
740
+ prompt = BasePrompt(versions={"v1": "Version 1", "v2": "Version 2"})
741
+
742
+ with pytest.raises(ValueError, match="Version ID 'v1' already exists"):
743
+ prompt.append_version(version_id="v1", content="Duplicate")
744
+
745
+ async def test_append_version_creates_outdated_prompt(self):
746
+ """Test that append_version creates an OutdatedPrompt for compiled prompts."""
747
+ prompt = BasePrompt(
748
+ versions={"v1": "Version {name}"},
749
+ variables_definition=SamplePromptVariables,
750
+ )
751
+ variables = SamplePromptVariables(name="Test", age=25)
752
+
753
+ # Compile to create a compiled prompt entry
754
+ compiled_result = prompt.compile(variables)
755
+
756
+ # Append new version
757
+ prompt.append_version(version_id="v2", content="New {name}")
758
+
759
+ # Check that compiled prompt now references OutdatedPrompt
760
+ compiled_prompt = None
761
+ for cp in _compiled_prompt_registry.values():
762
+ if cp.value == compiled_result:
763
+ compiled_prompt = cp
764
+ break
765
+
766
+ assert compiled_prompt is not None
767
+ assert isinstance(compiled_prompt.prompt, OutdatedPrompt)
768
+
769
+ async def test_update_default_version_id_changes_default(self):
770
+ """Test that update_default_version_id changes the default version."""
771
+ prompt = BasePrompt(
772
+ versions={"v1": "Version 1", "v2": "Version 2", "v3": "Version 3"},
773
+ default_version_id="v1",
774
+ )
775
+
776
+ prompt.update_default_version_id("v2")
777
+
778
+ assert prompt.get_default_version_id() == "v2"
779
+
780
+ async def test_update_default_version_id_raises_error_for_nonexistent_version(self):
781
+ """Test that update_default_version_id raises ValueError for nonexistent version."""
782
+ prompt = BasePrompt(versions={"v1": "Version 1"}, default_version_id="v1")
783
+
784
+ with pytest.raises(ValueError, match="Version ID 'nonexistent' does not exist"):
785
+ prompt.update_default_version_id("nonexistent")
786
+
787
+ async def test_update_default_version_id_noop_when_same_version(self):
788
+ """Test that update_default_version_id does nothing when setting to same version."""
789
+ prompt = BasePrompt(
790
+ versions={"v1": "Version 1", "v2": "Version 2"}, default_version_id="v1"
791
+ )
792
+
793
+ # This should not raise an error or change anything
794
+ prompt.update_default_version_id("v1")
795
+
796
+ assert prompt.get_default_version_id() == "v1"
797
+
798
+ async def test_update_default_version_id_creates_outdated_prompt(self):
799
+ """Test that update_default_version_id creates OutdatedPrompt for compiled prompts."""
800
+ prompt = BasePrompt(
801
+ versions={"v1": "Version {name}", "v2": "Other {name}"},
802
+ variables_definition=SamplePromptVariables,
803
+ default_version_id="v1",
804
+ )
805
+ variables = SamplePromptVariables(name="Test", age=25)
806
+
807
+ # Compile to create a compiled prompt entry
808
+ compiled_result = prompt.compile(variables)
809
+
810
+ # Update default version
811
+ prompt.update_default_version_id("v2")
812
+
813
+ # Check that compiled prompt now references OutdatedPrompt
814
+ compiled_prompt = None
815
+ for cp in _compiled_prompt_registry.values():
816
+ if cp.value == compiled_result:
817
+ compiled_prompt = cp
818
+ break
819
+
820
+ assert compiled_prompt is not None
821
+ assert isinstance(compiled_prompt.prompt, OutdatedPrompt)
822
+
823
+ async def test_append_version_with_variables_compiles_correctly(self):
824
+ """Test that appended versions compile correctly with variables."""
825
+ prompt = BasePrompt(
826
+ versions={"v1": "Hello {{name}}"},
827
+ variables_definition=SamplePromptVariables,
828
+ )
829
+
830
+ prompt.append_version(version_id="v2", content="Hi {{name}}, you are {{age}}")
831
+
832
+ variables = SamplePromptVariables(name="Alice", age=30)
833
+
834
+ result_v1 = prompt.compile(variables, version_id="v1")
835
+ result_v2 = prompt.compile(variables, version_id="v2")
836
+
837
+ assert result_v1 == "Hello Alice"
838
+ assert result_v2 == "Hi Alice, you are 30"
839
+
840
+ async def test_update_default_version_id_affects_compile_without_version_id(self):
841
+ """Test that updating default version affects compile() without version_id parameter."""
842
+ prompt = BasePrompt(
843
+ versions={"v1": "Version 1", "v2": "Version 2"},
844
+ variables_definition=SamplePromptVariables,
845
+ default_version_id="v1",
846
+ )
847
+
848
+ variables = SamplePromptVariables(name="Test", age=25)
849
+
850
+ # Initially uses v1
851
+ result_before = prompt.compile(variables)
852
+ assert result_before == "Version 1"
853
+
854
+ # Update default to v2
855
+ prompt.update_default_version_id("v2")
856
+
857
+ # Now uses v2 as default
858
+ result_after = prompt.compile(variables)
859
+ assert result_after == "Version 2"
860
+
861
+
862
+ class TestUpdatePromptRegistry:
863
+ """Tests for the update_prompt_registry function."""
864
+
865
+ @pytest.mark.asyncio
866
+ async def test_update_prompt_registry_new_prompt_raises_error(self):
867
+ """Test that update_prompt_registry raises KeyError for new prompts."""
868
+ new_prompt = BaseUntypedPrompt(versions={"v1": "New version"})
869
+
870
+ with pytest.raises(KeyError):
871
+ BasePrompt.update_prompt_registry(new_prompt)
872
+
873
+ @pytest.mark.asyncio
874
+ async def test_update_prompt_registry_existing_prompt(self):
875
+ """Test that update_prompt_registry updates existing prompts."""
876
+ original_prompt = BaseUntypedPrompt(versions={"v1": "Original version"})
877
+ # Manually add to registry with variable definitions
878
+ _prompt_registry[original_prompt.id] = BasePrompt.from_untyped(
879
+ original_prompt, variables_definition=SamplePromptVariables
880
+ )
881
+
882
+ # Temporarily remove from registry to create updated UntypedPrompt
883
+ del _prompt_registry[original_prompt.id]
884
+
885
+ updated_prompt = BaseUntypedPrompt(
886
+ id=original_prompt.id, versions={"v1": "Updated version"}
887
+ )
888
+
889
+ # Add back to registry to simulate existing
890
+ _prompt_registry[original_prompt.id] = BasePrompt.from_untyped(
891
+ original_prompt, variables_definition=SamplePromptVariables
892
+ )
893
+
894
+ result = BasePrompt.update_prompt_registry(updated_prompt)
895
+
896
+ assert result.id == original_prompt.id
897
+ versions = result.get_versions()
898
+ assert versions["v1"] == "Updated version"
899
+ assert (
900
+ result.variables_definition == SamplePromptVariables
901
+ ) # Should retain original var_def
902
+
903
+
904
+ @pytest.mark.asyncio
905
+ class TestFilePromptStorage:
906
+ """Tests for the FilePromptStorage class."""
907
+
908
+ async def test_save_existing_prompt(self):
909
+ """Test saving an existing prompt updates its data."""
910
+ with tempfile.TemporaryDirectory() as temp_dir:
911
+ storage = _FilePromptStorage(directory=temp_dir)
912
+
913
+ # Create the prompt in registry first
914
+ prompt = BaseUntypedPrompt(versions={"v1": "Initial version"})
915
+ BasePrompt.from_untyped(prompt)
916
+
917
+ storage.save(prompt) # Use await for async call
918
+
919
+ # Update the prompt
920
+ updated_prompt = BaseUntypedPrompt(
921
+ id=prompt.id, versions={"v1": "Updated version"}
922
+ )
923
+
924
+ is_new = storage.save(updated_prompt) # Use await for async call
925
+
926
+ assert not is_new
927
+
928
+ # Verify the file content
929
+ filepath = os.path.join(temp_dir, f"{prompt.id}.json")
930
+ with open(filepath, "r") as f:
931
+ data = json.load(f)
932
+
933
+ assert data["versions"]["v1"] == "Updated version"
934
+
935
+
936
+ class TestVariablesDefinitionToSchema:
937
+ """Tests for variables_definition_to_schema function."""
938
+
939
+ def test_nonetype_returns_empty_schema(self):
940
+ """Test that NoneType returns an empty schema."""
941
+ from pixie.prompts.prompt import variables_definition_to_schema
942
+
943
+ result = variables_definition_to_schema(NoneType)
944
+ assert result == {"type": "object", "properties": {}}
945
+
946
+ def test_prompt_variables_returns_json_schema(self):
947
+ """Test that PromptVariables subclass returns proper JSON schema."""
948
+ from pixie.prompts.prompt import variables_definition_to_schema
949
+
950
+ result = variables_definition_to_schema(SamplePromptVariables)
951
+
952
+ assert result["type"] == "object"
953
+ assert "properties" in result
954
+ assert "name" in result["properties"]
955
+ assert "age" in result["properties"]
956
+ assert "city" in result["properties"]
957
+
958
+ # Check required fields
959
+ assert "required" in result
960
+ assert "name" in result["required"]
961
+ assert "age" in result["required"]
962
+ # city has default, so not required
963
+
964
+ def test_different_variable_classes_have_different_schemas(self):
965
+ """Test that different PromptVariables classes produce different schemas."""
966
+ from pixie.prompts.prompt import variables_definition_to_schema
967
+
968
+ schema1 = variables_definition_to_schema(SamplePromptVariables)
969
+ schema2 = variables_definition_to_schema(AnotherPromptVariables)
970
+
971
+ assert schema1 != schema2
972
+ assert "name" in schema1["properties"]
973
+ assert "greeting" in schema2["properties"]
974
+
975
+
976
+ @pytest.mark.asyncio
977
+ class TestBasePromptFromUntyped:
978
+ """Tests for BasePrompt.from_untyped with schema validation."""
979
+
980
+ async def test_from_untyped_with_compatible_schema(self):
981
+ """Test that from_untyped works with compatible schema."""
982
+ untyped = BaseUntypedPrompt(
983
+ versions={"v1": "Hello {name}"},
984
+ default_version_id="v1",
985
+ variables_schema={
986
+ "type": "object",
987
+ "properties": {
988
+ "name": {"type": "string"},
989
+ "age": {"type": "integer"},
990
+ "city": {"type": "string"},
991
+ },
992
+ "required": ["name", "age"],
993
+ },
994
+ )
995
+
996
+ # SamplePromptVariables has name, age, city (with default)
997
+ # This should be compatible
998
+ typed = BasePrompt.from_untyped(untyped, SamplePromptVariables)
999
+
1000
+ assert typed.variables_definition == SamplePromptVariables
1001
+ assert typed.get_versions() == {"v1": "Hello {name}"}
1002
+
1003
+ async def test_from_untyped_with_incompatible_schema_raises_error(self):
1004
+ """Test that from_untyped raises ValueError for incompatible schema."""
1005
+ # Base schema requires 'greeting' and 'topic'
1006
+ untyped = BaseUntypedPrompt(
1007
+ versions={"v1": "Hello"},
1008
+ default_version_id="v1",
1009
+ variables_schema={
1010
+ "type": "object",
1011
+ "properties": {
1012
+ "greeting": {"type": "string"},
1013
+ "topic": {"type": "string"},
1014
+ },
1015
+ "required": ["greeting", "topic"],
1016
+ },
1017
+ )
1018
+
1019
+ # SamplePromptVariables has name, age, city - incompatible
1020
+ with pytest.raises(TypeError):
1021
+ BasePrompt.from_untyped(untyped, SamplePromptVariables)
1022
+
1023
+ async def test_from_untyped_with_nonetype(self):
1024
+ """Test that from_untyped works with NoneType."""
1025
+ untyped = BaseUntypedPrompt(
1026
+ versions={"v1": "Hello"},
1027
+ default_version_id="v1",
1028
+ )
1029
+
1030
+ typed = BasePrompt.from_untyped(untyped, NoneType)
1031
+
1032
+ assert typed.variables_definition == NoneType
1033
+ result = typed.compile()
1034
+ assert result == "Hello"
1035
+
1036
+ async def test_from_untyped_preserves_id(self):
1037
+ """Test that from_untyped preserves the prompt ID."""
1038
+ untyped = BaseUntypedPrompt(
1039
+ versions={"v1": "Hello"},
1040
+ default_version_id="v1",
1041
+ id="test_id_123",
1042
+ )
1043
+
1044
+ typed = BasePrompt.from_untyped(untyped, NoneType)
1045
+
1046
+ assert typed.id == "test_id_123"
1047
+
1048
+ async def test_from_untyped_with_empty_schema_accepts_any_variables(self):
1049
+ """Test that empty schema accepts any variables definition."""
1050
+ untyped = BaseUntypedPrompt(
1051
+ versions={"v1": "Hello {name}"},
1052
+ default_version_id="v1",
1053
+ variables_schema={"type": "object", "properties": {}},
1054
+ )
1055
+
1056
+ # Should accept any PromptVariables subclass
1057
+ typed1 = BasePrompt.from_untyped(untyped, SamplePromptVariables)
1058
+ typed2 = BasePrompt.from_untyped(untyped, AnotherPromptVariables)
1059
+
1060
+ assert typed1.variables_definition == SamplePromptVariables
1061
+ assert typed2.variables_definition == AnotherPromptVariables
1062
+
1063
+
1064
+ @pytest.mark.asyncio
1065
+ class TestBaseUntypedPromptWithSchema:
1066
+ """Tests for BaseUntypedPrompt with variables_schema parameter."""
1067
+
1068
+ async def test_init_with_variables_schema(self):
1069
+ """Test initialization with explicit variables_schema."""
1070
+ schema = {
1071
+ "type": "object",
1072
+ "properties": {"name": {"type": "string"}},
1073
+ "required": ["name"],
1074
+ }
1075
+
1076
+ prompt = BaseUntypedPrompt(
1077
+ versions={"v1": "Hello"},
1078
+ variables_schema=schema,
1079
+ )
1080
+
1081
+ result_schema = prompt.get_variables_schema()
1082
+ assert result_schema == schema
1083
+
1084
+ async def test_init_without_variables_schema_uses_empty(self):
1085
+ """Test that missing variables_schema defaults to empty schema."""
1086
+ prompt = BaseUntypedPrompt(versions={"v1": "Hello"})
1087
+
1088
+ result_schema = prompt.get_variables_schema()
1089
+ assert result_schema == {"type": "object", "properties": {}}
1090
+
1091
+ async def test_get_variables_schema_returns_copy(self):
1092
+ """Test that get_variables_schema returns a deep copy."""
1093
+ original_schema = {
1094
+ "type": "object",
1095
+ "properties": {"name": {"type": "string"}},
1096
+ }
1097
+
1098
+ prompt = BaseUntypedPrompt(
1099
+ versions={"v1": "Hello"},
1100
+ variables_schema=original_schema,
1101
+ )
1102
+
1103
+ result_schema = prompt.get_variables_schema()
1104
+ result_schema["properties"]["name"]["type"] = "integer"
1105
+
1106
+ # Original should be unchanged
1107
+ check_schema = prompt.get_variables_schema()
1108
+ assert check_schema["properties"]["name"]["type"] == "string"
1109
+
1110
+
1111
+ @pytest.mark.asyncio
1112
+ class TestOutdatedPromptGetMethods:
1113
+ """Tests for OutdatedPrompt async get methods."""
1114
+
1115
+ async def test_outdated_prompt_get_versions(self):
1116
+ """Test that OutdatedPrompt.get_versions works."""
1117
+ outdated = OutdatedPrompt(
1118
+ versions={"v1": "Test", "v2": "Test2"},
1119
+ default_version_id="v1",
1120
+ id="test_id",
1121
+ variables_definition=NoneType,
1122
+ )
1123
+
1124
+ versions = outdated.get_versions()
1125
+ assert versions == {"v1": "Test", "v2": "Test2"}
1126
+
1127
+ async def test_outdated_prompt_get_default_version_id(self):
1128
+ """Test that OutdatedPrompt.get_default_version_id works."""
1129
+ outdated = OutdatedPrompt(
1130
+ versions={"v1": "Test"},
1131
+ default_version_id="v1",
1132
+ id="test_id",
1133
+ variables_definition=NoneType,
1134
+ )
1135
+
1136
+ default_id = outdated.get_default_version_id()
1137
+ assert default_id == "v1"
1138
+
1139
+ async def test_outdated_prompt_preserves_variables_definition(self):
1140
+ """Test that OutdatedPrompt preserves variables_definition."""
1141
+ prompt = BasePrompt(
1142
+ versions={"v1": "Hello {name}"},
1143
+ variables_definition=SamplePromptVariables,
1144
+ )
1145
+
1146
+ outdated = OutdatedPrompt.from_prompt(prompt)
1147
+
1148
+ assert outdated.variables_definition == SamplePromptVariables
1149
+
1150
+
1151
+ class TestGetCompiledPrompt:
1152
+ """Tests for get_compiled_prompt function."""
1153
+
1154
+ def setup_method(self):
1155
+ """Clear registries before each test."""
1156
+ _compiled_prompt_registry.clear()
1157
+ _prompt_registry.clear()
1158
+
1159
+ def teardown_method(self):
1160
+ """Clear registries after each test."""
1161
+ _compiled_prompt_registry.clear()
1162
+ _prompt_registry.clear()
1163
+
1164
+ def test_get_compiled_prompt_empty_registry(self):
1165
+ """Test get_compiled_prompt returns None when registry is empty."""
1166
+ from pixie.prompts.prompt import get_compiled_prompt
1167
+
1168
+ result = get_compiled_prompt("any text")
1169
+ assert result is None
1170
+
1171
+ def test_get_compiled_prompt_direct_match(self):
1172
+ """Test get_compiled_prompt finds prompt by direct id match."""
1173
+ from pixie.prompts.prompt import get_compiled_prompt
1174
+
1175
+ prompt = BasePrompt(versions="Test prompt")
1176
+ compiled_text = prompt.compile()
1177
+
1178
+ # The compiled text should be in the registry with its id as key
1179
+ result = get_compiled_prompt(compiled_text)
1180
+ assert result is not None
1181
+ assert result.value == "Test prompt"
1182
+ assert result.prompt == prompt
1183
+ assert result.version_id == DEFAULT_VERSION_ID
1184
+
1185
+ def test_get_compiled_prompt_value_match(self):
1186
+ """Test get_compiled_prompt finds prompt by value when direct match fails."""
1187
+ from pixie.prompts.prompt import get_compiled_prompt
1188
+
1189
+ prompt = BasePrompt(versions="Test prompt")
1190
+ prompt.compile()
1191
+
1192
+ # Create a new string with same content but different id
1193
+ same_text = "Test prompt"
1194
+
1195
+ result = get_compiled_prompt(same_text)
1196
+ assert result is not None
1197
+ assert result.value == "Test prompt"
1198
+ assert result.prompt == prompt
1199
+
1200
+ def test_get_compiled_prompt_with_variables(self):
1201
+ """Test get_compiled_prompt works with variable-substituted prompts."""
1202
+ from pixie.prompts.prompt import get_compiled_prompt
1203
+
1204
+ prompt = BasePrompt(
1205
+ versions="Hello, {{name}}!", variables_definition=SamplePromptVariables
1206
+ )
1207
+ variables = SamplePromptVariables(name="Alice", age=25)
1208
+ compiled_text = prompt.compile(variables)
1209
+
1210
+ result = get_compiled_prompt(compiled_text)
1211
+ assert result is not None
1212
+ assert result.value == "Hello, Alice!"
1213
+ assert result.prompt == prompt
1214
+ assert result.variables == variables
1215
+
1216
+ def test_get_compiled_prompt_json_string(self):
1217
+ """Test get_compiled_prompt can find prompts in JSON strings."""
1218
+ from pixie.prompts.prompt import get_compiled_prompt
1219
+
1220
+ prompt = BasePrompt(versions="Test prompt")
1221
+ compiled_text = prompt.compile()
1222
+
1223
+ # Embed the compiled text in JSON
1224
+ json_text = f'{{"message": "{compiled_text}", "other": "data"}}'
1225
+
1226
+ result = get_compiled_prompt(json_text)
1227
+ assert result is not None
1228
+ assert result.value == "Test prompt"
1229
+ assert result.prompt == prompt
1230
+
1231
+ def test_get_compiled_prompt_json_array(self):
1232
+ """Test get_compiled_prompt can find prompts in JSON arrays."""
1233
+ from pixie.prompts.prompt import get_compiled_prompt
1234
+
1235
+ prompt = BasePrompt(versions="Test prompt")
1236
+ compiled_text = prompt.compile()
1237
+
1238
+ # Embed the compiled text in JSON array
1239
+ json_text = f'["start", "{compiled_text}", "end"]'
1240
+
1241
+ result = get_compiled_prompt(json_text)
1242
+ assert result is not None
1243
+ assert result.value == "Test prompt"
1244
+ assert result.prompt == prompt
1245
+
1246
+ def test_get_compiled_prompt_nested_json(self):
1247
+ """Test get_compiled_prompt can find prompts in nested JSON structures."""
1248
+ from pixie.prompts.prompt import get_compiled_prompt
1249
+
1250
+ prompt = BasePrompt(versions="Test prompt")
1251
+ compiled_text = prompt.compile()
1252
+
1253
+ # Embed in nested structure
1254
+ json_text = f'{{"data": {{"messages": ["{compiled_text}"]}}}}'
1255
+
1256
+ result = get_compiled_prompt(json_text)
1257
+ assert result is not None
1258
+ assert result.value == "Test prompt"
1259
+ assert result.prompt == prompt
1260
+
1261
+ def test_get_compiled_prompt_no_match(self):
1262
+ """Test get_compiled_prompt returns None when no match is found."""
1263
+ from pixie.prompts.prompt import get_compiled_prompt
1264
+
1265
+ prompt = BasePrompt(versions="Test prompt")
1266
+ prompt.compile() # Add to registry
1267
+
1268
+ result = get_compiled_prompt("Completely different text")
1269
+ assert result is None
1270
+
1271
+ def test_get_compiled_prompt_invalid_json(self):
1272
+ """Test get_compiled_prompt handles invalid JSON gracefully."""
1273
+ from pixie.prompts.prompt import get_compiled_prompt
1274
+
1275
+ prompt = BasePrompt(versions="Test prompt")
1276
+ prompt.compile() # Add to registry
1277
+
1278
+ # Invalid JSON should not crash
1279
+ result = get_compiled_prompt("{invalid json")
1280
+ assert result is None
1281
+
1282
+ def test_get_compiled_prompt_empty_string(self):
1283
+ """Test get_compiled_prompt with empty string."""
1284
+ from pixie.prompts.prompt import get_compiled_prompt
1285
+
1286
+ result = get_compiled_prompt("")
1287
+ assert result is None
1288
+
1289
+ def test_get_compiled_prompt_multiple_prompts(self):
1290
+ """Test get_compiled_prompt with multiple prompts in registry."""
1291
+ from pixie.prompts.prompt import get_compiled_prompt
1292
+
1293
+ prompt1 = BasePrompt(versions="First prompt")
1294
+ prompt2 = BasePrompt(versions="Second prompt")
1295
+
1296
+ compiled1 = prompt1.compile()
1297
+ compiled2 = prompt2.compile()
1298
+
1299
+ result1 = get_compiled_prompt(compiled1)
1300
+ result2 = get_compiled_prompt(compiled2)
1301
+
1302
+ assert result1 is not None
1303
+ assert result1.prompt == prompt1
1304
+ assert result2 is not None
1305
+ assert result2.prompt == prompt2
1306
+
1307
+ @pytest.mark.asyncio
1308
+ async def test_get_compiled_prompt_outdated_prompt(self):
1309
+ """Test get_compiled_prompt handles outdated prompts correctly."""
1310
+ from pixie.prompts.prompt import get_compiled_prompt
1311
+
1312
+ prompt = BasePrompt(versions={"v1": "Original"})
1313
+ compiled_text = prompt.compile()
1314
+
1315
+ # Update the prompt to create an outdated version
1316
+ prompt._update(versions={"v1": "Updated"})
1317
+
1318
+ result = get_compiled_prompt(compiled_text)
1319
+ assert result is not None
1320
+ assert isinstance(result.prompt, OutdatedPrompt)
1321
+ assert result.value == "Original"