pixie-prompts 0.1.6__tar.gz → 0.1.8__tar.gz

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,138 @@
1
+ Metadata-Version: 2.4
2
+ Name: pixie-prompts
3
+ Version: 0.1.8
4
+ Summary: Code-first, type-safe prompt management
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Author: Yiou Li
8
+ Author-email: yol@gopixie.ai
9
+ Requires-Python: >=3.10,<4.0
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Provides-Extra: server
18
+ Requires-Dist: brotli (>=1.2.0,<2.0.0) ; extra == "server"
19
+ Requires-Dist: colorlog (>=6.10.1) ; extra == "server"
20
+ Requires-Dist: dotenv (>=0.9.9) ; extra == "server"
21
+ Requires-Dist: fastapi (>=0.128.0) ; extra == "server"
22
+ Requires-Dist: jinja2 (>=3.1.6,<4.0.0)
23
+ Requires-Dist: jsonsubschema (>=0.0.7,<0.0.8)
24
+ Requires-Dist: openai[server] (>=2.15.0,<3.0.0) ; extra == "server"
25
+ Requires-Dist: pydantic (>=2.12.5,<3.0.0)
26
+ Requires-Dist: pydantic-ai-slim (>=1.39.0) ; extra == "server"
27
+ Requires-Dist: strawberry-graphql (>=0.288.1) ; extra == "server"
28
+ Requires-Dist: uvicorn (>=0.40.0) ; extra == "server"
29
+ Requires-Dist: watchdog (>=6.0.0) ; extra == "server"
30
+ Requires-Dist: watchfiles[server] (>=1.1.1,<2.0.0) ; extra == "server"
31
+ Project-URL: Changelog, https://github.com/yiouli/pixie-prompts/commits/main/
32
+ Project-URL: Documentation, https://yiouli.github.io/pixie-prompts/
33
+ Project-URL: Homepage, https://gopixie.ai
34
+ Project-URL: Issues, https://github.com/yiouli/pixie-prompts/issues
35
+ Project-URL: Repository, https://github.com/yiouli/pixie-prompts
36
+ Description-Content-Type: text/markdown
37
+
38
+ # pixie-prompts
39
+ **Code-first, type-checked prompt management.**
40
+ Manage prompt locally in your codebase, with Jinja rendering, variable type-hint and validations.
41
+
42
+ [**Demo**](https://github.com/user-attachments/assets/aba55aca-1ad3-4f25-97f9-db0f8e67dbe6)
43
+
44
+ [**Try it live**](https://gopixie.ai/?url=https%3A%2F%2Fpixie-prompts-examples.vercel.app%2Fgraphql)
45
+
46
+ ## Setup
47
+
48
+
49
+
50
+
51
+ In your project folder, install **pixie-prompts[server]** Python package:
52
+ ```bash
53
+ pip install pixie-prompts[server]
54
+ ```
55
+ > Note: you can install **pixie-prompts** without the server extras for your production build.
56
+
57
+ Start the local dev server and open the web UI by running:
58
+ ```bash
59
+ pp
60
+ ```
61
+ > Note: The web-browser would automatically open [http://localhost:8000](http://localhost:8000). You can also access the web UI at [gopixie.ai](https://gopixie.ai).
62
+
63
+ To test prompts, create *.env* file with LLM API key(s):
64
+ ```ini
65
+ # .env
66
+ OPENAI_API_KEY=...
67
+ GEMINI_API_KEY=...
68
+ ```
69
+
70
+ ## Register Prompt
71
+
72
+ In your code, create a new prompt using `create_prompt`:
73
+ ```python
74
+ # prompts.py
75
+ from pixie.prompts import create_prompt
76
+
77
+ simple_prompt = create_prompt('simple_prompt')
78
+ ```
79
+
80
+ Your prompt would automatically appear in the web UI after your code is saved.
81
+
82
+
83
+ ## Manage Prompt
84
+
85
+ You can create new version(s) of a prompt in the web UI.
86
+
87
+ Once saved from web UI, it will be assigned a new version id, and the content would be saved in your codebase at */.pixie/prompts/<prompt_name>/<version_id>.jinja*.
88
+
89
+ > Note: it's recommended to only edit your prompts via the web UI to get type-hint and validation.
90
+
91
+
92
+ ## Define Variables
93
+
94
+ For prompt that has variable(s) in it, define a class extending `pixie.prompts.Variables` (which extends `pydantic.BaseModel`. Then use the class type when registering your prompt.
95
+
96
+ ```python
97
+ # prompts.py
98
+ from pixie.prompts import Variables, create_prompt
99
+
100
+ class Person(Variables):
101
+ name: str
102
+ age: int
103
+
104
+ # Create a prompt with variables
105
+ typed_prompt = create_prompt('typed_prompt', Person)
106
+ ```
107
+
108
+ Other than using dict, you can define your variable class in anyway that's permissible in Pydantic. I.e. you can define your field as basic types such as `str`, `int`, `bool`, you can have a `list` of permissible items, you can use `Union` type, and you can have nested `Variable` field.
109
+
110
+ The web UI will parse your variable definitions and use it to decide input fields, type-hints and validations.
111
+
112
+
113
+ ## Use Prompt
114
+
115
+ Compile your prompt into string with the `compile` function on the prompt object. Pass in the Variables object (if defined) for your prompt as argument.
116
+ ```python
117
+ # demo.py
118
+
119
+ from pixie.prompts import Variables, create_prompt
120
+
121
+ simple_prompt = create_prompt('simple_prompt')
122
+
123
+ class Person(Variables):
124
+ name: str
125
+ age: int
126
+
127
+ # Create a prompt with variables
128
+ typed_prompt = create_prompt('typed_prompt', Person)
129
+
130
+ simple_prompt_str = simple_prompt.compile()
131
+ typed_prompt_str = typed_prompt.compile(Person(name="Jane", age=30))
132
+
133
+ ```
134
+
135
+
136
+
137
+ Check out more [examples](https://github.com/yiouli/pixie-prompts-examples/blob/main/examples/prompts.py).
138
+
@@ -0,0 +1,100 @@
1
+ # pixie-prompts
2
+ **Code-first, type-checked prompt management.**
3
+ Manage prompt locally in your codebase, with Jinja rendering, variable type-hint and validations.
4
+
5
+ [**Demo**](https://github.com/user-attachments/assets/aba55aca-1ad3-4f25-97f9-db0f8e67dbe6)
6
+
7
+ [**Try it live**](https://gopixie.ai/?url=https%3A%2F%2Fpixie-prompts-examples.vercel.app%2Fgraphql)
8
+
9
+ ## Setup
10
+
11
+
12
+
13
+
14
+ In your project folder, install **pixie-prompts[server]** Python package:
15
+ ```bash
16
+ pip install pixie-prompts[server]
17
+ ```
18
+ > Note: you can install **pixie-prompts** without the server extras for your production build.
19
+
20
+ Start the local dev server and open the web UI by running:
21
+ ```bash
22
+ pp
23
+ ```
24
+ > Note: The web-browser would automatically open [http://localhost:8000](http://localhost:8000). You can also access the web UI at [gopixie.ai](https://gopixie.ai).
25
+
26
+ To test prompts, create *.env* file with LLM API key(s):
27
+ ```ini
28
+ # .env
29
+ OPENAI_API_KEY=...
30
+ GEMINI_API_KEY=...
31
+ ```
32
+
33
+ ## Register Prompt
34
+
35
+ In your code, create a new prompt using `create_prompt`:
36
+ ```python
37
+ # prompts.py
38
+ from pixie.prompts import create_prompt
39
+
40
+ simple_prompt = create_prompt('simple_prompt')
41
+ ```
42
+
43
+ Your prompt would automatically appear in the web UI after your code is saved.
44
+
45
+
46
+ ## Manage Prompt
47
+
48
+ You can create new version(s) of a prompt in the web UI.
49
+
50
+ Once saved from web UI, it will be assigned a new version id, and the content would be saved in your codebase at */.pixie/prompts/<prompt_name>/<version_id>.jinja*.
51
+
52
+ > Note: it's recommended to only edit your prompts via the web UI to get type-hint and validation.
53
+
54
+
55
+ ## Define Variables
56
+
57
+ For prompt that has variable(s) in it, define a class extending `pixie.prompts.Variables` (which extends `pydantic.BaseModel`. Then use the class type when registering your prompt.
58
+
59
+ ```python
60
+ # prompts.py
61
+ from pixie.prompts import Variables, create_prompt
62
+
63
+ class Person(Variables):
64
+ name: str
65
+ age: int
66
+
67
+ # Create a prompt with variables
68
+ typed_prompt = create_prompt('typed_prompt', Person)
69
+ ```
70
+
71
+ Other than using dict, you can define your variable class in anyway that's permissible in Pydantic. I.e. you can define your field as basic types such as `str`, `int`, `bool`, you can have a `list` of permissible items, you can use `Union` type, and you can have nested `Variable` field.
72
+
73
+ The web UI will parse your variable definitions and use it to decide input fields, type-hints and validations.
74
+
75
+
76
+ ## Use Prompt
77
+
78
+ Compile your prompt into string with the `compile` function on the prompt object. Pass in the Variables object (if defined) for your prompt as argument.
79
+ ```python
80
+ # demo.py
81
+
82
+ from pixie.prompts import Variables, create_prompt
83
+
84
+ simple_prompt = create_prompt('simple_prompt')
85
+
86
+ class Person(Variables):
87
+ name: str
88
+ age: int
89
+
90
+ # Create a prompt with variables
91
+ typed_prompt = create_prompt('typed_prompt', Person)
92
+
93
+ simple_prompt_str = simple_prompt.compile()
94
+ typed_prompt_str = typed_prompt.compile(Person(name="Jane", age=30))
95
+
96
+ ```
97
+
98
+
99
+
100
+ Check out more [examples](https://github.com/yiouli/pixie-prompts-examples/blob/main/examples/prompts.py).
@@ -18,6 +18,85 @@ _storage_observer: Observer | None = None # type: ignore
18
18
  _storage_reload_task: asyncio.Task | None = None
19
19
 
20
20
 
21
+ def _preload_pixie_modules(cwd: Path) -> None:
22
+ """Pre-load pixie modules from the local source directory.
23
+
24
+ This ensures that when dynamically loaded modules import pixie.registry,
25
+ they get the same module instance as the rest of the application.
26
+
27
+ Without this, namespace package collisions can occur when pixie-prompts
28
+ is installed as a package while pixie-sdk-py is run from source. The
29
+ dynamic module loading via importlib.util.spec_from_file_location() can
30
+ create separate module instances, leading to separate _registry dicts.
31
+
32
+ Args:
33
+ cwd: The current working directory to search for pixie modules.
34
+ """
35
+ pixie_dir = cwd / "pixie"
36
+ if not pixie_dir.is_dir():
37
+ return
38
+
39
+ # Find all pixie submodules that exist locally and pre-import them
40
+ # This ensures they're in sys.modules before any dynamic loading
41
+ pixie_modules_to_preload = []
42
+
43
+ for py_file in pixie_dir.rglob("*.py"):
44
+ if py_file.name.startswith("_"):
45
+ continue
46
+ # Skip if in __pycache__ or similar
47
+ if "__pycache__" in py_file.parts:
48
+ continue
49
+
50
+ relative_path = py_file.relative_to(cwd)
51
+ module_name = str(relative_path.with_suffix("")).replace("/", ".")
52
+ pixie_modules_to_preload.append((module_name, py_file))
53
+
54
+ # Sort to ensure parent modules are loaded before children
55
+ pixie_modules_to_preload.sort(key=lambda x: x[0])
56
+
57
+ for module_name, py_file in pixie_modules_to_preload:
58
+ if module_name in sys.modules:
59
+ continue
60
+
61
+ # Ensure parent packages are in sys.modules
62
+ parts = module_name.split(".")
63
+ for i in range(1, len(parts)):
64
+ parent_name = ".".join(parts[:i])
65
+ if parent_name not in sys.modules:
66
+ parent_path = cwd / Path(*parts[:i])
67
+ init_file = parent_path / "__init__.py"
68
+ if init_file.exists():
69
+ spec = importlib.util.spec_from_file_location(
70
+ parent_name, init_file
71
+ )
72
+ if spec and spec.loader:
73
+ module = importlib.util.module_from_spec(spec)
74
+ sys.modules[parent_name] = module
75
+ try:
76
+ spec.loader.exec_module(module)
77
+ except Exception:
78
+ pass
79
+ else:
80
+ # Create a namespace package
81
+ import types
82
+
83
+ ns_module = types.ModuleType(parent_name)
84
+ ns_module.__path__ = [str(parent_path)]
85
+ sys.modules[parent_name] = ns_module
86
+
87
+ # Now load the actual module
88
+ spec = importlib.util.spec_from_file_location(module_name, py_file)
89
+ if spec and spec.loader:
90
+ try:
91
+ module = importlib.util.module_from_spec(spec)
92
+ sys.modules[module_name] = module
93
+ spec.loader.exec_module(module)
94
+ except Exception:
95
+ # Don't fail if a module can't be loaded - it might have
96
+ # dependencies that aren't available yet
97
+ pass
98
+
99
+
21
100
  def discover_and_load_modules():
22
101
  """Discover and load all Python files that use pixie.prompts.create_prompt, or pixie.create_prompt.
23
102
 
@@ -34,6 +113,10 @@ def discover_and_load_modules():
34
113
  if str(cwd) not in sys.path:
35
114
  sys.path.insert(0, str(cwd))
36
115
 
116
+ # Pre-load pixie modules from local source to avoid namespace package collisions
117
+ # This ensures dynamically loaded modules use the same pixie.registry instance
118
+ _preload_pixie_modules(cwd)
119
+
37
120
  loaded_count = 0
38
121
  for py_file in python_files:
39
122
  # Skip __init__.py, private files, and anything in site-packages/venv
@@ -58,6 +141,12 @@ def discover_and_load_modules():
58
141
  # Load the module with a unique name based on path
59
142
  relative_path = py_file.relative_to(cwd)
60
143
  module_name = str(relative_path.with_suffix("")).replace("/", ".")
144
+
145
+ # Skip if module was already loaded (e.g., during preload)
146
+ if module_name in sys.modules:
147
+ loaded_count += 1
148
+ continue
149
+
61
150
  spec = importlib.util.spec_from_file_location(module_name, py_file)
62
151
  if spec and spec.loader:
63
152
  try:
@@ -3,6 +3,7 @@
3
3
  from datetime import datetime
4
4
  import json
5
5
  import logging
6
+ import os
6
7
  from typing import Any, Optional, cast, get_args
7
8
 
8
9
  from graphql import GraphQLError
@@ -94,6 +95,11 @@ class LlmCallResult:
94
95
  reasoning: str | None
95
96
 
96
97
 
98
+ def is_demo_mode() -> bool:
99
+ is_demo_mode = os.getenv("IS_DEMO_MODE", "0") in ("1", "true", "True")
100
+ return is_demo_mode
101
+
102
+
97
103
  @strawberry.type
98
104
  class Query:
99
105
  """GraphQL queries."""
@@ -105,6 +111,8 @@ class Query:
105
111
  Returns:
106
112
  A list of model names supported by the server.
107
113
  """
114
+ if is_demo_mode():
115
+ return ["openai:gpt-4o-mini"]
108
116
  return list(get_args(KnownModelName.__value__))
109
117
 
110
118
  @strawberry.field
@@ -219,6 +227,8 @@ class Mutation:
219
227
  GraphQLError: If the LLM call fails.
220
228
  """
221
229
  try:
230
+ if is_demo_mode():
231
+ model = "openai:gpt-4o-mini"
222
232
  template = jinja2.Template(prompt_template)
223
233
  prompt = template.render(**(cast(dict[str, Any], variables) or {}))
224
234
  print(prompt)
@@ -243,7 +253,6 @@ class Mutation:
243
253
  elif part.part_kind == "system-prompt":
244
254
  part.content = part.content.replace(prompt_placeholder, prompt)
245
255
 
246
- print(pydantic_messages)
247
256
  # Replace the placeholder in input messages
248
257
  response = await model_request(
249
258
  model=model,
@@ -309,6 +318,8 @@ class Mutation:
309
318
  Returns:
310
319
  The updated BasePrompt object.
311
320
  """
321
+ if is_demo_mode():
322
+ raise GraphQLError("Modifications are not allowed in demo mode.")
312
323
  prompt_with_registration = get_prompt((str(prompt_id)))
313
324
  if prompt_with_registration is None:
314
325
  raise GraphQLError(f"Prompt with id '{prompt_id}' not found.")
@@ -338,6 +349,8 @@ class Mutation:
338
349
  Returns:
339
350
  True if the update was successful.
340
351
  """
352
+ if is_demo_mode():
353
+ raise GraphQLError("Modifications are not allowed in demo mode.")
341
354
  prompt_with_registration = get_prompt((str(prompt_id)))
342
355
  if prompt_with_registration is None:
343
356
  raise GraphQLError(f"Prompt with id '{prompt_id}' not found.")
@@ -93,7 +93,9 @@ def create_app() -> FastAPI:
93
93
  # Discover and load applications on every app creation (including reloads)
94
94
  discover_and_load_modules()
95
95
 
96
- dotenv.load_dotenv(os.getcwd() + "/.env")
96
+ env_path = os.getcwd() + "/.env"
97
+
98
+ dotenv.load_dotenv(env_path)
97
99
  lifespan = init_prompt_storage()
98
100
 
99
101
  app = FastAPI(
@@ -220,6 +222,7 @@ def start_server(
220
222
  port=port,
221
223
  loop="asyncio",
222
224
  reload=reload,
225
+ reload_includes=[".env"],
223
226
  factory=True,
224
227
  log_config=None,
225
228
  )
@@ -4,7 +4,7 @@ packages = [
4
4
  { include = "pixie" },
5
5
  ]
6
6
 
7
- version = "0.1.6"
7
+ version = "0.1.8"
8
8
  description = "Code-first, type-safe prompt management"
9
9
  authors = ["Yiou Li <yol@gopixie.ai>"]
10
10
  license = "MIT"
@@ -31,9 +31,10 @@ watchdog = {version = ">=6.0.0", optional = true}
31
31
  pydantic-ai-slim = {version = ">=1.39.0", optional = true}
32
32
  brotli = {version = "^1.2.0", optional = true}
33
33
  openai = {version = "^2.15.0", extras = ["server"]}
34
+ watchfiles = {version = "^1.1.1", extras = ["server"]}
34
35
 
35
36
  [tool.poetry.extras]
36
- server = ["fastapi", "strawberry-graphql", "uvicorn", "colorlog", "dotenv", "watchdog", "pydantic-ai-slim", "brotli", "openai"]
37
+ server = ["fastapi", "strawberry-graphql", "uvicorn", "colorlog", "dotenv", "watchdog", "pydantic-ai-slim", "brotli", "openai", "watchfiles"]
37
38
 
38
39
  [tool.poetry.group.dev.dependencies]
39
40
  pytest = ">=7.4,<9.0"
@@ -1,39 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: pixie-prompts
3
- Version: 0.1.6
4
- Summary: Code-first, type-safe prompt management
5
- License: MIT
6
- License-File: LICENSE
7
- Author: Yiou Li
8
- Author-email: yol@gopixie.ai
9
- Requires-Python: >=3.10,<4.0
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.11
14
- Classifier: Programming Language :: Python :: 3.12
15
- Classifier: Programming Language :: Python :: 3.13
16
- Classifier: Programming Language :: Python :: 3.14
17
- Provides-Extra: server
18
- Requires-Dist: brotli (>=1.2.0,<2.0.0) ; extra == "server"
19
- Requires-Dist: colorlog (>=6.10.1) ; extra == "server"
20
- Requires-Dist: dotenv (>=0.9.9) ; extra == "server"
21
- Requires-Dist: fastapi (>=0.128.0) ; extra == "server"
22
- Requires-Dist: jinja2 (>=3.1.6,<4.0.0)
23
- Requires-Dist: jsonsubschema (>=0.0.7,<0.0.8)
24
- Requires-Dist: openai[server] (>=2.15.0,<3.0.0) ; extra == "server"
25
- Requires-Dist: pydantic (>=2.12.5,<3.0.0)
26
- Requires-Dist: pydantic-ai-slim (>=1.39.0) ; extra == "server"
27
- Requires-Dist: strawberry-graphql (>=0.288.1) ; extra == "server"
28
- Requires-Dist: uvicorn (>=0.40.0) ; extra == "server"
29
- Requires-Dist: watchdog (>=6.0.0) ; extra == "server"
30
- Project-URL: Changelog, https://github.com/yiouli/pixie-prompts/commits/main/
31
- Project-URL: Documentation, https://yiouli.github.io/pixie-prompts/
32
- Project-URL: Homepage, https://gopixie.ai
33
- Project-URL: Issues, https://github.com/yiouli/pixie-prompts/issues
34
- Project-URL: Repository, https://github.com/yiouli/pixie-prompts
35
- Description-Content-Type: text/markdown
36
-
37
- # pixie-prompts
38
- Code-first, type-checked prompt management.
39
-
@@ -1,2 +0,0 @@
1
- # pixie-prompts
2
- Code-first, type-checked prompt management.
File without changes