textprompts 0.0.1__py3-none-any.whl → 0.0.2__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.
- textprompts/__init__.py +11 -2
- textprompts/__main__.py +8 -0
- textprompts/_parser.py +21 -8
- textprompts/cli.py +1 -1
- textprompts/config.py +15 -0
- textprompts/loaders.py +12 -4
- textprompts/models.py +36 -11
- textprompts/placeholder_utils.py +5 -2
- textprompts/prompt_string.py +60 -0
- textprompts/safe_string.py +3 -108
- textprompts/savers.py +2 -2
- {textprompts-0.0.1.dist-info → textprompts-0.0.2.dist-info}/METADATA +19 -16
- textprompts-0.0.2.dist-info/RECORD +17 -0
- {textprompts-0.0.1.dist-info → textprompts-0.0.2.dist-info}/WHEEL +1 -1
- textprompts-0.0.1.dist-info/RECORD +0 -15
- {textprompts-0.0.1.dist-info → textprompts-0.0.2.dist-info}/entry_points.txt +0 -0
textprompts/__init__.py
CHANGED
@@ -1,4 +1,10 @@
|
|
1
|
-
from .config import
|
1
|
+
from .config import (
|
2
|
+
MetadataMode,
|
3
|
+
get_metadata,
|
4
|
+
set_metadata,
|
5
|
+
skip_metadata,
|
6
|
+
warn_on_ignored_metadata,
|
7
|
+
)
|
2
8
|
from .errors import (
|
3
9
|
FileMissingError,
|
4
10
|
InvalidMetadataError,
|
@@ -8,7 +14,7 @@ from .errors import (
|
|
8
14
|
)
|
9
15
|
from .loaders import load_prompt, load_prompts
|
10
16
|
from .models import Prompt, PromptMeta
|
11
|
-
from .
|
17
|
+
from .prompt_string import PromptString, SafeString
|
12
18
|
from .savers import save_prompt
|
13
19
|
|
14
20
|
__all__ = [
|
@@ -17,10 +23,13 @@ __all__ = [
|
|
17
23
|
"save_prompt",
|
18
24
|
"Prompt",
|
19
25
|
"PromptMeta",
|
26
|
+
"PromptString",
|
20
27
|
"SafeString",
|
21
28
|
"MetadataMode",
|
22
29
|
"set_metadata",
|
23
30
|
"get_metadata",
|
31
|
+
"skip_metadata",
|
32
|
+
"warn_on_ignored_metadata",
|
24
33
|
"TextPromptsError",
|
25
34
|
"FileMissingError",
|
26
35
|
"MissingMetadataError",
|
textprompts/__main__.py
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
"""Command-line entry point for ``python -m textprompts``.""" # pragma: no cover
|
2
|
+
|
3
|
+
from textprompts.cli import main # pragma: no cover
|
4
|
+
|
5
|
+
__all__ = ["main"] # pragma: no cover
|
6
|
+
|
7
|
+
if __name__ == "__main__": # pragma: no cover - small entrypoint wrapper
|
8
|
+
main() # pragma: no cover
|
textprompts/_parser.py
CHANGED
@@ -4,13 +4,13 @@ from typing import Optional
|
|
4
4
|
|
5
5
|
try:
|
6
6
|
import tomllib
|
7
|
-
except ImportError:
|
7
|
+
except ImportError: # pragma: no cover - Python <3.11 fallback
|
8
8
|
import tomli as tomllib # type: ignore[import-not-found, no-redef]
|
9
9
|
|
10
|
-
from .config import MetadataMode
|
10
|
+
from .config import MetadataMode, warn_on_ignored_metadata
|
11
11
|
from .errors import InvalidMetadataError, MalformedHeaderError, MissingMetadataError
|
12
12
|
from .models import Prompt, PromptMeta
|
13
|
-
from .
|
13
|
+
from .prompt_string import PromptString
|
14
14
|
|
15
15
|
DELIM = "---"
|
16
16
|
|
@@ -59,8 +59,21 @@ def parse_file(path: Path, *, metadata_mode: MetadataMode) -> Prompt:
|
|
59
59
|
|
60
60
|
# Handle IGNORE mode - treat entire file as body
|
61
61
|
if metadata_mode == MetadataMode.IGNORE:
|
62
|
+
if (
|
63
|
+
warn_on_ignored_metadata()
|
64
|
+
and raw.startswith(DELIM)
|
65
|
+
and raw.find(DELIM, len(DELIM)) != -1
|
66
|
+
):
|
67
|
+
import warnings
|
68
|
+
|
69
|
+
warnings.warn(
|
70
|
+
"Metadata detected but ignored; use set_metadata('allow') or skip_metadata(skip_warning=True) to silence",
|
71
|
+
stacklevel=2,
|
72
|
+
)
|
62
73
|
ignore_meta = PromptMeta(title=path.stem)
|
63
|
-
return Prompt(
|
74
|
+
return Prompt(
|
75
|
+
path=path, meta=ignore_meta, prompt=PromptString(textwrap.dedent(raw))
|
76
|
+
)
|
64
77
|
|
65
78
|
# For STRICT and ALLOW modes, try to parse front matter
|
66
79
|
try:
|
@@ -72,7 +85,7 @@ def parse_file(path: Path, *, metadata_mode: MetadataMode) -> Prompt:
|
|
72
85
|
f"{e}. If this file has no metadata and starts with '---', "
|
73
86
|
f"use meta=MetadataMode.IGNORE to skip metadata parsing."
|
74
87
|
) from e
|
75
|
-
raise
|
88
|
+
raise # pragma: no cover - reraised to preserve stack
|
76
89
|
|
77
90
|
meta: Optional[PromptMeta] = None
|
78
91
|
if header_txt is not None:
|
@@ -114,7 +127,7 @@ def parse_file(path: Path, *, metadata_mode: MetadataMode) -> Prompt:
|
|
114
127
|
) from e
|
115
128
|
except InvalidMetadataError:
|
116
129
|
raise
|
117
|
-
except Exception as e:
|
130
|
+
except Exception as e: # pragma: no cover - unlikely generic error
|
118
131
|
raise InvalidMetadataError(f"Invalid metadata: {e}") from e
|
119
132
|
|
120
133
|
else:
|
@@ -129,11 +142,11 @@ def parse_file(path: Path, *, metadata_mode: MetadataMode) -> Prompt:
|
|
129
142
|
meta = PromptMeta()
|
130
143
|
|
131
144
|
# Always ensure we have metadata with a title
|
132
|
-
if not meta:
|
145
|
+
if not meta: # pragma: no cover - meta is never falsy but kept for safety
|
133
146
|
meta = PromptMeta()
|
134
147
|
|
135
148
|
# Use filename as title if not provided
|
136
149
|
if meta.title is None:
|
137
150
|
meta.title = path.stem
|
138
151
|
|
139
|
-
return Prompt(path=path, meta=meta,
|
152
|
+
return Prompt(path=path, meta=meta, prompt=PromptString(textwrap.dedent(body)))
|
textprompts/cli.py
CHANGED
textprompts/config.py
CHANGED
@@ -30,6 +30,7 @@ class MetadataMode(Enum):
|
|
30
30
|
|
31
31
|
# Global configuration variable
|
32
32
|
_METADATA_MODE: MetadataMode = MetadataMode.IGNORE
|
33
|
+
_WARN_ON_IGNORED_META: bool = True
|
33
34
|
|
34
35
|
|
35
36
|
def set_metadata(mode: Union[MetadataMode, str]) -> None:
|
@@ -82,6 +83,20 @@ def get_metadata() -> MetadataMode:
|
|
82
83
|
return _METADATA_MODE
|
83
84
|
|
84
85
|
|
86
|
+
def skip_metadata(*, skip_warning: bool = False) -> None:
|
87
|
+
"""Convenience setter for ignoring metadata with optional warnings."""
|
88
|
+
|
89
|
+
global _WARN_ON_IGNORED_META
|
90
|
+
_WARN_ON_IGNORED_META = not skip_warning # pragma: no cover
|
91
|
+
set_metadata(MetadataMode.IGNORE) # pragma: no cover - simple wrapper
|
92
|
+
|
93
|
+
|
94
|
+
def warn_on_ignored_metadata() -> bool:
|
95
|
+
"""Return whether warnings for ignored metadata are enabled."""
|
96
|
+
|
97
|
+
return _WARN_ON_IGNORED_META
|
98
|
+
|
99
|
+
|
85
100
|
def _resolve_metadata_mode(meta: Union[MetadataMode, str, None]) -> MetadataMode:
|
86
101
|
"""
|
87
102
|
Resolve the metadata mode from parameters and global config.
|
textprompts/loaders.py
CHANGED
@@ -96,17 +96,25 @@ def load_prompts(
|
|
96
96
|
if pth.is_dir():
|
97
97
|
itr: Iterable[Path] = pth.rglob(glob) if recursive else pth.glob(glob)
|
98
98
|
for f in itr:
|
99
|
-
if
|
99
|
+
if (
|
100
|
+
max_files and file_count >= max_files
|
101
|
+
): # pragma: no cover - boundary check
|
100
102
|
from .errors import TextPromptsError
|
101
103
|
|
102
|
-
raise TextPromptsError(
|
104
|
+
raise TextPromptsError(
|
105
|
+
f"Exceeded max_files limit of {max_files}"
|
106
|
+
) # pragma: no cover - boundary check
|
103
107
|
collected.append(load_prompt(f, meta=meta))
|
104
108
|
file_count += 1
|
105
109
|
else:
|
106
|
-
if
|
110
|
+
if (
|
111
|
+
max_files and file_count >= max_files
|
112
|
+
): # pragma: no cover - boundary check
|
107
113
|
from .errors import TextPromptsError
|
108
114
|
|
109
|
-
raise TextPromptsError(
|
115
|
+
raise TextPromptsError(
|
116
|
+
f"Exceeded max_files limit of {max_files}"
|
117
|
+
) # pragma: no cover - boundary check
|
110
118
|
collected.append(load_prompt(pth, meta=meta))
|
111
119
|
file_count += 1
|
112
120
|
|
textprompts/models.py
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
from datetime import date
|
2
2
|
from pathlib import Path
|
3
|
-
from typing import Union
|
3
|
+
from typing import Any, Union
|
4
4
|
|
5
5
|
from pydantic import BaseModel, Field, field_validator
|
6
6
|
|
7
|
-
from .
|
7
|
+
from .prompt_string import PromptString
|
8
8
|
|
9
9
|
|
10
10
|
class PromptMeta(BaseModel):
|
@@ -18,25 +18,50 @@ class PromptMeta(BaseModel):
|
|
18
18
|
class Prompt(BaseModel):
|
19
19
|
path: Path
|
20
20
|
meta: Union[PromptMeta, None]
|
21
|
-
|
21
|
+
prompt: PromptString
|
22
22
|
|
23
|
-
@field_validator("
|
23
|
+
@field_validator("prompt")
|
24
24
|
@classmethod
|
25
|
-
def
|
25
|
+
def prompt_not_empty(cls, v: str) -> PromptString:
|
26
26
|
if not v.strip():
|
27
27
|
raise ValueError("Prompt body is empty")
|
28
|
-
return
|
28
|
+
return PromptString(v)
|
29
29
|
|
30
30
|
def __repr__(self) -> str:
|
31
31
|
if self.meta and self.meta.title:
|
32
32
|
if self.meta.version:
|
33
33
|
return (
|
34
34
|
f"Prompt(title='{self.meta.title}', version='{self.meta.version}')"
|
35
|
+
" # use .format() or str()"
|
35
36
|
)
|
36
|
-
|
37
|
-
|
38
|
-
else:
|
39
|
-
return f"Prompt(path='{self.path}')"
|
37
|
+
return f"Prompt(title='{self.meta.title}') # use .format() or str()"
|
38
|
+
return f"Prompt(path='{self.path}') # use .format() or str()"
|
40
39
|
|
41
40
|
def __str__(self) -> str:
|
42
|
-
return str(self.
|
41
|
+
return str(self.prompt)
|
42
|
+
|
43
|
+
@property
|
44
|
+
def body(self) -> PromptString:
|
45
|
+
import warnings
|
46
|
+
|
47
|
+
warnings.warn(
|
48
|
+
"Prompt.body is deprecated; use .prompt instead",
|
49
|
+
DeprecationWarning,
|
50
|
+
stacklevel=2,
|
51
|
+
)
|
52
|
+
return self.prompt
|
53
|
+
|
54
|
+
def __len__(self) -> int:
|
55
|
+
return len(self.prompt)
|
56
|
+
|
57
|
+
def __getitem__(self, item: int | slice) -> str:
|
58
|
+
return self.prompt[item]
|
59
|
+
|
60
|
+
def __add__(self, other: str) -> str:
|
61
|
+
return str(self.prompt) + str(other)
|
62
|
+
|
63
|
+
def strip(self, *args: Any, **kwargs: Any) -> str:
|
64
|
+
return self.prompt.strip(*args, **kwargs)
|
65
|
+
|
66
|
+
def format(self, *args: Any, **kwargs: Any) -> str:
|
67
|
+
return self.prompt.format(*args, **kwargs)
|
textprompts/placeholder_utils.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
"""
|
2
2
|
Utility functions for extracting and validating placeholders in format strings.
|
3
3
|
|
4
|
-
This module provides robust placeholder extraction and validation for
|
4
|
+
This module provides robust placeholder extraction and validation for PromptString
|
5
5
|
formatting operations.
|
6
6
|
"""
|
7
7
|
|
@@ -53,7 +53,10 @@ def extract_placeholders(text: str) -> Set[str]:
|
|
53
53
|
|
54
54
|
|
55
55
|
def validate_format_args(
|
56
|
-
placeholders: Set[str],
|
56
|
+
placeholders: Set[str],
|
57
|
+
args: Tuple[Any, ...],
|
58
|
+
kwargs: Dict[str, Any],
|
59
|
+
skip_validation: bool = False,
|
57
60
|
) -> None:
|
58
61
|
"""
|
59
62
|
Validate that format arguments match the placeholders in the template.
|
@@ -0,0 +1,60 @@
|
|
1
|
+
from typing import Any, Set
|
2
|
+
|
3
|
+
from pydantic import GetCoreSchemaHandler
|
4
|
+
from pydantic_core import core_schema
|
5
|
+
|
6
|
+
from .placeholder_utils import extract_placeholders, validate_format_args
|
7
|
+
|
8
|
+
|
9
|
+
class PromptString(str):
|
10
|
+
"""String subclass that validates ``format()`` calls."""
|
11
|
+
|
12
|
+
placeholders: Set[str]
|
13
|
+
|
14
|
+
def __new__(cls, value: str) -> "PromptString":
|
15
|
+
instance = str.__new__(cls, value)
|
16
|
+
instance.placeholders = extract_placeholders(value)
|
17
|
+
return instance
|
18
|
+
|
19
|
+
def format(self, *args: Any, **kwargs: Any) -> str:
|
20
|
+
"""Format with validation and optional partial formatting."""
|
21
|
+
skip_validation = kwargs.pop("skip_validation", False)
|
22
|
+
source = str(self).strip()
|
23
|
+
if skip_validation:
|
24
|
+
return self._partial_format(*args, source=source, **kwargs)
|
25
|
+
validate_format_args(self.placeholders, args, kwargs, skip_validation=False)
|
26
|
+
return str.format(source, *args, **kwargs)
|
27
|
+
|
28
|
+
def _partial_format(
|
29
|
+
self, *args: Any, source: str | None = None, **kwargs: Any
|
30
|
+
) -> str:
|
31
|
+
"""Partial formatting - replace placeholders that have values."""
|
32
|
+
all_kwargs = kwargs.copy()
|
33
|
+
for i, arg in enumerate(args):
|
34
|
+
all_kwargs[str(i)] = arg
|
35
|
+
|
36
|
+
result = source if source is not None else str(self)
|
37
|
+
for placeholder in self.placeholders:
|
38
|
+
if placeholder in all_kwargs:
|
39
|
+
pattern = f"{{{placeholder}}}"
|
40
|
+
if pattern in result:
|
41
|
+
try:
|
42
|
+
result = result.replace(pattern, str(all_kwargs[placeholder]))
|
43
|
+
except (KeyError, ValueError): # pragma: no cover - defensive
|
44
|
+
pass
|
45
|
+
return result
|
46
|
+
|
47
|
+
def __repr__(self) -> str:
|
48
|
+
return f"PromptString({str.__repr__(self)}, placeholders={self.placeholders})"
|
49
|
+
|
50
|
+
@classmethod
|
51
|
+
def __get_pydantic_core_schema__(
|
52
|
+
cls, source_type: Any, handler: GetCoreSchemaHandler
|
53
|
+
) -> core_schema.CoreSchema:
|
54
|
+
return core_schema.no_info_after_validator_function(
|
55
|
+
cls, core_schema.str_schema()
|
56
|
+
)
|
57
|
+
|
58
|
+
|
59
|
+
# Backwards compatibility alias
|
60
|
+
SafeString = PromptString
|
textprompts/safe_string.py
CHANGED
@@ -1,110 +1,5 @@
|
|
1
|
-
|
1
|
+
"""Backward-compatible alias module.""" # pragma: no cover
|
2
2
|
|
3
|
-
from
|
4
|
-
from pydantic_core import core_schema
|
3
|
+
from .prompt_string import PromptString, SafeString # pragma: no cover
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
class SafeString(str):
|
10
|
-
"""
|
11
|
-
A string subclass that validates format() calls to ensure all placeholders are provided.
|
12
|
-
|
13
|
-
This prevents common errors where format variables are missing, making prompt templates
|
14
|
-
more reliable and easier to debug.
|
15
|
-
|
16
|
-
Attributes:
|
17
|
-
placeholders: Set of placeholder names found in the string
|
18
|
-
"""
|
19
|
-
|
20
|
-
placeholders: Set[str]
|
21
|
-
|
22
|
-
def __new__(cls, value: str) -> "SafeString":
|
23
|
-
"""Create a new SafeString instance with extracted placeholders."""
|
24
|
-
instance = str.__new__(cls, value)
|
25
|
-
instance.placeholders = extract_placeholders(value)
|
26
|
-
return instance
|
27
|
-
|
28
|
-
def format(self, *args: Any, **kwargs: Any) -> str:
|
29
|
-
"""
|
30
|
-
Format the string with configurable validation behavior.
|
31
|
-
|
32
|
-
By default (skip_validation=False), this method validates that all placeholders
|
33
|
-
have corresponding values and raises ValueError if any are missing.
|
34
|
-
|
35
|
-
When skip_validation=True, it performs partial formatting, replacing only
|
36
|
-
the placeholders for which values are provided.
|
37
|
-
|
38
|
-
Args:
|
39
|
-
*args: Positional arguments for formatting
|
40
|
-
skip_validation: If True, perform partial formatting without validation
|
41
|
-
**kwargs: Keyword arguments for formatting
|
42
|
-
|
43
|
-
Returns:
|
44
|
-
The formatted string
|
45
|
-
|
46
|
-
Raises:
|
47
|
-
ValueError: If skip_validation=False and any placeholder is missing
|
48
|
-
"""
|
49
|
-
skip_validation = kwargs.pop('skip_validation', False)
|
50
|
-
if skip_validation:
|
51
|
-
# Partial formatting - replace only available placeholders
|
52
|
-
return self._partial_format(*args, **kwargs)
|
53
|
-
else:
|
54
|
-
# Strict formatting - validate all placeholders are provided
|
55
|
-
validate_format_args(self.placeholders, args, kwargs, skip_validation=False)
|
56
|
-
return str.format(self, *args, **kwargs)
|
57
|
-
|
58
|
-
def _partial_format(self, *args: Any, **kwargs: Any) -> str:
|
59
|
-
"""
|
60
|
-
Perform partial formatting, replacing only the placeholders that have values.
|
61
|
-
|
62
|
-
Args:
|
63
|
-
*args: Positional arguments for formatting
|
64
|
-
**kwargs: Keyword arguments for formatting
|
65
|
-
|
66
|
-
Returns:
|
67
|
-
The partially formatted string
|
68
|
-
"""
|
69
|
-
# Convert positional args to keyword args
|
70
|
-
all_kwargs = kwargs.copy()
|
71
|
-
for i, arg in enumerate(args):
|
72
|
-
all_kwargs[str(i)] = arg
|
73
|
-
|
74
|
-
# Build a format string with only available placeholders
|
75
|
-
result = str(self)
|
76
|
-
|
77
|
-
# Replace placeholders one by one if they have values
|
78
|
-
for placeholder in self.placeholders:
|
79
|
-
if placeholder in all_kwargs:
|
80
|
-
# Create a single-placeholder format string
|
81
|
-
placeholder_pattern = f"{{{placeholder}}}"
|
82
|
-
if placeholder_pattern in result:
|
83
|
-
try:
|
84
|
-
# Replace this specific placeholder
|
85
|
-
result = result.replace(
|
86
|
-
placeholder_pattern, str(all_kwargs[placeholder])
|
87
|
-
)
|
88
|
-
except (KeyError, ValueError):
|
89
|
-
# If replacement fails, leave the placeholder as is
|
90
|
-
pass
|
91
|
-
|
92
|
-
return result
|
93
|
-
|
94
|
-
def __str__(self) -> str:
|
95
|
-
"""Return the string representation."""
|
96
|
-
return str.__str__(self)
|
97
|
-
|
98
|
-
def __repr__(self) -> str:
|
99
|
-
"""Return the string representation for debugging."""
|
100
|
-
return f"SafeString({str.__repr__(self)}, placeholders={self.placeholders})"
|
101
|
-
|
102
|
-
@classmethod
|
103
|
-
def __get_pydantic_core_schema__(
|
104
|
-
cls, source_type: Any, handler: GetCoreSchemaHandler
|
105
|
-
) -> core_schema.CoreSchema:
|
106
|
-
"""Support for Pydantic v2 schema generation."""
|
107
|
-
return core_schema.no_info_after_validator_function(
|
108
|
-
cls,
|
109
|
-
core_schema.str_schema(),
|
110
|
-
)
|
5
|
+
__all__ = ["PromptString", "SafeString"] # pragma: no cover
|
textprompts/savers.py
CHANGED
@@ -25,7 +25,7 @@ def save_prompt(path: Union[str, Path], content: Union[str, Prompt]) -> None:
|
|
25
25
|
>>> prompt = Prompt(
|
26
26
|
... path=Path("my_prompt.txt"),
|
27
27
|
... meta=PromptMeta(title="Assistant", version="1.0.0", description="A helpful AI"),
|
28
|
-
...
|
28
|
+
... prompt="You are a helpful assistant."
|
29
29
|
... )
|
30
30
|
>>> save_prompt("my_prompt.txt", prompt)
|
31
31
|
"""
|
@@ -59,7 +59,7 @@ version = ""
|
|
59
59
|
|
60
60
|
lines.append("---")
|
61
61
|
lines.append("")
|
62
|
-
lines.append(str(content.
|
62
|
+
lines.append(str(content.prompt))
|
63
63
|
|
64
64
|
path.write_text("\n".join(lines), encoding="utf-8")
|
65
65
|
else:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: textprompts
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.2
|
4
4
|
Summary: Minimal text-based prompt-loader with TOML front-matter
|
5
5
|
Keywords: prompts,toml,frontmatter,template
|
6
6
|
Author: Jan Siml
|
@@ -76,7 +76,7 @@ import textprompts
|
|
76
76
|
prompt = textprompts.load_prompt("greeting.txt")
|
77
77
|
|
78
78
|
# Use it safely - all placeholders must be provided
|
79
|
-
message = prompt.
|
79
|
+
message = prompt.prompt.format(
|
80
80
|
customer_name="Alice",
|
81
81
|
company_name="ACME Corp",
|
82
82
|
issue_type="billing question",
|
@@ -86,19 +86,22 @@ message = prompt.body.format(
|
|
86
86
|
print(message)
|
87
87
|
|
88
88
|
# Or use partial formatting when needed
|
89
|
-
partial = prompt.
|
89
|
+
partial = prompt.prompt.format(
|
90
90
|
customer_name="Alice",
|
91
91
|
company_name="ACME Corp",
|
92
92
|
skip_validation=True
|
93
93
|
)
|
94
94
|
# Result: "Hello Alice!\n\nWelcome to ACME Corp. We're here to help you with {issue_type}.\n\nBest regards,\n{agent_name}"
|
95
|
+
|
96
|
+
# Prompt objects expose `.meta` and `.prompt`.
|
97
|
+
# Use `prompt.prompt.format()` for safe formatting or `str(prompt)` for raw text.
|
95
98
|
```
|
96
99
|
|
97
100
|
**Even simpler** - no metadata required:
|
98
101
|
```python
|
99
102
|
# simple_prompt.txt contains just: "Analyze this data: {data}"
|
100
103
|
prompt = textprompts.load_prompt("simple_prompt.txt") # Just works!
|
101
|
-
result = prompt.
|
104
|
+
result = prompt.prompt.format(data="sales figures")
|
102
105
|
```
|
103
106
|
|
104
107
|
## Core Features
|
@@ -108,9 +111,9 @@ result = prompt.body.format(data="sales figures")
|
|
108
111
|
Never ship a prompt with missing variables again:
|
109
112
|
|
110
113
|
```python
|
111
|
-
from textprompts import
|
114
|
+
from textprompts import PromptString
|
112
115
|
|
113
|
-
template =
|
116
|
+
template = PromptString("Hello {name}, your order {order_id} is {status}")
|
114
117
|
|
115
118
|
# ✅ Strict formatting - all placeholders must be provided
|
116
119
|
result = template.format(name="Alice", order_id="12345", status="shipped")
|
@@ -190,14 +193,14 @@ response = openai.chat.completions.create(
|
|
190
193
|
messages=[
|
191
194
|
{
|
192
195
|
"role": "system",
|
193
|
-
"content": system_prompt.
|
196
|
+
"content": system_prompt.prompt.format(
|
194
197
|
company_name="ACME Corp",
|
195
198
|
support_level="premium"
|
196
199
|
)
|
197
200
|
},
|
198
201
|
{
|
199
202
|
"role": "user",
|
200
|
-
"content": user_prompt.
|
203
|
+
"content": user_prompt.prompt.format(
|
201
204
|
query="How do I return an item?",
|
202
205
|
customer_tier="premium"
|
203
206
|
)
|
@@ -208,7 +211,7 @@ response = openai.chat.completions.create(
|
|
208
211
|
|
209
212
|
### Function Calling (Tool Definitions)
|
210
213
|
|
211
|
-
Yes, you can version control your
|
214
|
+
Yes, you can version control your whole tool schemas too:
|
212
215
|
|
213
216
|
```python
|
214
217
|
# tools/search_products.txt
|
@@ -253,7 +256,7 @@ from textprompts import load_prompt
|
|
253
256
|
|
254
257
|
# Load and parse the tool definition
|
255
258
|
tool_prompt = load_prompt("tools/search_products.txt")
|
256
|
-
tool_schema = json.loads(tool_prompt.
|
259
|
+
tool_schema = json.loads(tool_prompt.prompt)
|
257
260
|
|
258
261
|
# Use with OpenAI
|
259
262
|
response = openai.chat.completions.create(
|
@@ -338,7 +341,7 @@ Load a single prompt file.
|
|
338
341
|
|
339
342
|
Returns a `Prompt` object with:
|
340
343
|
- `prompt.meta`: Metadata from TOML front-matter (always present)
|
341
|
-
- `prompt.
|
344
|
+
- `prompt.prompt`: The prompt content as a `PromptString`
|
342
345
|
- `prompt.path`: Path to the original file
|
343
346
|
|
344
347
|
### `load_prompts(*paths, recursive=False, glob="*.txt", meta=None, max_files=1000)`
|
@@ -385,14 +388,14 @@ save_prompt("my_prompt.txt", "You are a helpful assistant.")
|
|
385
388
|
save_prompt("my_prompt.txt", prompt_object)
|
386
389
|
```
|
387
390
|
|
388
|
-
### `
|
391
|
+
### `PromptString`
|
389
392
|
|
390
393
|
A string subclass that validates `format()` calls:
|
391
394
|
|
392
395
|
```python
|
393
|
-
from textprompts import
|
396
|
+
from textprompts import PromptString
|
394
397
|
|
395
|
-
template =
|
398
|
+
template = PromptString("Hello {name}, you are {role}")
|
396
399
|
|
397
400
|
# Strict formatting (default) - all placeholders required
|
398
401
|
result = template.format(name="Alice", role="admin") # ✅ Works
|
@@ -460,8 +463,8 @@ textprompts validate prompts/
|
|
460
463
|
4. **Test your prompts**: Write unit tests for critical prompts
|
461
464
|
```python
|
462
465
|
def test_greeting_prompt():
|
463
|
-
|
464
|
-
|
466
|
+
prompt = load_prompt("greeting.txt")
|
467
|
+
result = prompt.prompt.format(customer_name="Test")
|
465
468
|
assert "Test" in result
|
466
469
|
```
|
467
470
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
textprompts/__init__.py,sha256=f17693e986f73978dc6ebecd683d3e812bc66b0f0691326957f6ea5e54fe7a87,831
|
2
|
+
textprompts/__main__.py,sha256=aee028e84759ad217eee72d3c3d7460ba1d46a25186d02b71c85078541db7f81,282
|
3
|
+
textprompts/_parser.py,sha256=40b8b64ca70398b8ee2093bb9e279d334fcfa3c31c7b6a30c37143596ea46c86,5793
|
4
|
+
textprompts/cli.py,sha256=8db7c429a182be9e8e69e11907fba3ee4465e391c580a96e25ee6ae566296ac0,855
|
5
|
+
textprompts/config.py,sha256=fe7c13db48a9081d2ba4fb0c925636d08f894d49a721307f885b5a360a7bc45d,3334
|
6
|
+
textprompts/errors.py,sha256=7eda4a1bdf4ee8a50b420886d2016a52923baa05a5b5a65d6f582e3e500290d2,354
|
7
|
+
textprompts/loaders.py,sha256=f6ddb9b6ea3f35d358f7d03e94e1aa781887e2d122f0b31345818ee458364083,4199
|
8
|
+
textprompts/models.py,sha256=ca13c427ec92b80c00b05fe2b4fc43ddda793ba2bb1bfeecb4ef0383f6afb0b7,2030
|
9
|
+
textprompts/placeholder_utils.py,sha256=a6df51ea0e7474a68a4f036fce0f647069be1395f1980db57decec7e01246772,4476
|
10
|
+
textprompts/prompt_string.py,sha256=4b7fefaca6c0c0a7edf67ae0213f77d32d21ff4735d458cd03c9f9bc885b804f,2156
|
11
|
+
textprompts/py.typed,sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,0
|
12
|
+
textprompts/safe_string.py,sha256=5bed8e93d404133591a9cc9d2f3260efe07de6030553d247edf3fceae025a198,195
|
13
|
+
textprompts/savers.py,sha256=0a46c5d2a1ffb24398e6380db3a090058c4bdd13369510ad2ee8506d143c47a1,2040
|
14
|
+
textprompts-0.0.2.dist-info/WHEEL,sha256=ab6157bc637547491fb4567cd7ddf26b04d63382916ca16c29a5c8e94c9c9ef7,79
|
15
|
+
textprompts-0.0.2.dist-info/entry_points.txt,sha256=f8f14b032092a81e77431911104853b39293c983c9390aa11fe023e8bcd5c049,54
|
16
|
+
textprompts-0.0.2.dist-info/METADATA,sha256=f78de4d094ffe79b3553596c21b98fe814e5cfc29d596c2b2402a5bb7c23e42f,14821
|
17
|
+
textprompts-0.0.2.dist-info/RECORD,,
|
@@ -1,15 +0,0 @@
|
|
1
|
-
textprompts/__init__.py,sha256=60050c36585fc2a0efb2a00832304f65614a9d521e95b3f1424aee3095608360,676
|
2
|
-
textprompts/_parser.py,sha256=95f6bfd6ff904f6ce301d1b480197fd1b6167c3f8bc67756955304ef2578c644,5159
|
3
|
-
textprompts/cli.py,sha256=3e69ba206767319db597d2c742233786c5d2f6a473a008e47e5cd42b94639b85,810
|
4
|
-
textprompts/config.py,sha256=9f7fad3b30e5033b85ec9f54dd9afa5746157e8b788c91b7392f0dbf703611af,2846
|
5
|
-
textprompts/errors.py,sha256=7eda4a1bdf4ee8a50b420886d2016a52923baa05a5b5a65d6f582e3e500290d2,354
|
6
|
-
textprompts/loaders.py,sha256=d30719e53faa4c5870955e3fea5050e32e3b2381def69978256922fce6fe259b,3895
|
7
|
-
textprompts/models.py,sha256=d200b9f90936c19c8d1c8cd1c2deead61f8459cab776e2b7555311dad87372bd,1241
|
8
|
-
textprompts/placeholder_utils.py,sha256=7f362bbf8cf865a2812310f5b73abe7a6107b048381da7a972698751334e85b3,4461
|
9
|
-
textprompts/py.typed,sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,0
|
10
|
-
textprompts/safe_string.py,sha256=55d537f3ef8b57c20e6b8442fe972a990ab55ae69bdf49200aad03819946a0f1,4048
|
11
|
-
textprompts/savers.py,sha256=4afb10e4b1e2189eb39504349e7e6414c0d7b16e61cbb7f9332a36dad3ebbd50,2036
|
12
|
-
textprompts-0.0.1.dist-info/WHEEL,sha256=607c46fee47e440c91332c738096ff0f5e54ca3b0818ee85462dd5172a38e793,79
|
13
|
-
textprompts-0.0.1.dist-info/entry_points.txt,sha256=f8f14b032092a81e77431911104853b39293c983c9390aa11fe023e8bcd5c049,54
|
14
|
-
textprompts-0.0.1.dist-info/METADATA,sha256=7ce0ad8eb5a7464379486174fb4619d12e51775236f82e047bd596e2993e3d38,14667
|
15
|
-
textprompts-0.0.1.dist-info/RECORD,,
|
File without changes
|