Skip to content

API Docs

PromptStore

promptstore.store.PromptStore

Source code in src/promptstore/store.py
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
class PromptStore:
    def __init__(self, location: Union[str, Path], readonly: bool = False):
        """Initialize a new PromptStore.

        Args:
            location: Path to the directory where prompts will be stored
            readonly: If True, the store will be read-only
        """
        self.location = Path(location)
        self.readonly = readonly
        self.promptstore_dir = self.location / ".promptstore"
        self.index_path = self.promptstore_dir / "index.json"
        self.aliases_path = self.promptstore_dir / "aliases.json"
        self._init_storage()

    def _init_storage(self):
        """Initialize the storage directory and index."""
        self.location.mkdir(parents=True, exist_ok=True)
        self.promptstore_dir.mkdir(exist_ok=True)

        if not self.index_path.exists() and not self.readonly:
            self._save_index({})
        if not self.aliases_path.exists() and not self.readonly:
            self._save_aliases({})

    def _load_index(self) -> Dict:
        """Load the prompt index."""
        if not self.index_path.exists():
            return {}
        with open(self.index_path, "r", encoding="utf-8") as f:
            return json.load(f)

    def _save_index(self, index: Dict):
        """Save the prompt index."""
        if self.readonly:
            raise ReadOnlyStoreError("Cannot modify a read-only prompt store")
        with open(self.index_path, "w", encoding="utf-8") as f:
            json.dump(index, f, indent=2, ensure_ascii=False)

    def _load_aliases(self) -> Dict:
        """Load the aliases mapping."""
        if not self.aliases_path.exists():
            return {}
        with open(self.aliases_path, "r", encoding="utf-8") as f:
            return json.load(f)

    def _save_aliases(self, aliases: Dict):
        """Save the aliases mapping."""
        if self.readonly:
            raise ReadOnlyStoreError("Cannot modify a read-only prompt store")
        with open(self.aliases_path, "w", encoding="utf-8") as f:
            json.dump(aliases, f, indent=2, ensure_ascii=False)

    def _parse_identifier(self, identifier: str) -> Dict[str, Optional[str]]:
        """Parse HuggingFace-style identifier.

        Format: namespace/name[@subset]

        Returns:
            Dict with keys: namespace, name, subset
        """
        # Check if it's a UUID
        if self._is_uuid(identifier):
            return {"uuid": identifier, "namespace": None, "name": None, "subset": None}

        # Parse namespace/name[@subset]
        pattern = r"^([^/]+)/([^@]+)(?:@(.+))?$"
        match = re.match(pattern, identifier)

        if not match:
            # If it doesn't match the pattern and is not a UUID,
            # treat it as a potential UUID for backward compatibility
            return {"uuid": identifier, "namespace": None, "name": None, "subset": None}

        namespace, name, subset = match.groups()

        return {"uuid": None, "namespace": namespace, "name": name, "subset": subset}

    def _is_uuid(self, value: str) -> bool:
        """Check if a string is a valid UUID."""
        try:
            uuid.UUID(value)
            return True
        except (ValueError, AttributeError):
            return False

    def _get_prompt_path(
        self,
        namespace: str,
        name: str,
        subset: Optional[str] = None,
        version: Optional[int] = None,
    ) -> Path:
        """Get the file path for a prompt.

        Args:
            namespace: The namespace
            name: The prompt name
            subset: Optional subset
            version: Optional version number

        Returns:
            Path to the prompt file
        """
        prompt_dir = self.location / namespace / name

        if subset:
            if version:
                return prompt_dir / "subsets" / f"{subset}_v{version}.md"
            else:
                return prompt_dir / "subsets" / f"{subset}.md"
        elif version:
            return prompt_dir / "versions" / f"v{version}.md"
        else:
            return prompt_dir / "prompt.md"

    def _read_prompt_file(self, file_path: Path) -> Prompt:
        """Read a prompt from a markdown file."""
        if not file_path.exists():
            raise PromptNotFoundError(f"Prompt file not found: {file_path}")

        with open(file_path, "r", encoding="utf-8") as f:
            content = f.read()

        return Prompt.from_markdown(content)

    def _write_prompt_file(self, file_path: Path, prompt: Prompt):
        """Write a prompt to a markdown file."""
        if self.readonly:
            raise ReadOnlyStoreError("Cannot modify a read-only prompt store")

        file_path.parent.mkdir(parents=True, exist_ok=True)
        with open(file_path, "w", encoding="utf-8") as f:
            f.write(prompt.to_markdown())

    @classmethod
    def from_dict(cls, prompts: Dict, readonly: bool = True) -> "PromptStore":
        """Create a PromptStore from a dictionary.

        Args:
            prompts: Dictionary of prompt data (old JSON format)
            readonly: If True, the store will be read-only

        Returns:
            PromptStore: A prompt store initialized with the given prompts
        """
        import tempfile

        temp_dir = Path(tempfile.mkdtemp())
        # Create store as writable first to populate it
        store = cls(temp_dir, readonly=False)

        # Convert old format to new format
        for uuid_val, data in prompts.items():
            # Create a prompt with the old data
            namespace = data.get("namespace", "default")
            name = data.get("name", f"prompt-{uuid_val[:8]}")

            prompt = Prompt(
                uuid=uuid_val,
                name=name,
                namespace=namespace,
                content=data["content"],
                description=data.get("description"),
                version=data.get("version", 1),
                tags=data.get("tags", []),
                created_at=data.get("created_at"),
                updated_at=data.get("updated_at"),
            )

            # Save to file system
            file_path = store._get_prompt_path(namespace, name)
            store._write_prompt_file(file_path, prompt)

            # Update index
            index = store._load_index()
            index[uuid_val] = {
                "namespace": namespace,
                "name": name,
                "path": str(file_path.relative_to(store.location)),
            }
            store._save_index(index)

            # Update aliases
            aliases = store._load_aliases()
            aliases[f"{namespace}/{name}"] = uuid_val
            store._save_aliases(aliases)

        # Now set to readonly if requested
        store.readonly = readonly
        return store

    @classmethod
    def from_file(cls, path: Union[str, Path], readonly: bool = True) -> "PromptStore":
        """Create a PromptStore from a JSON file.

        Args:
            path: Path to the JSON file containing prompts
            readonly: If True, the store will be read-only

        Returns:
            PromptStore: A prompt store initialized with the prompts from the file
        """
        path = Path(path)
        if not path.exists():
            raise FileNotFoundError(f"Prompt file not found: {path}")

        if path.is_file():
            # If it's a single JSON file (old format)
            with open(path, "r", encoding="utf-8") as f:
                prompts = json.load(f)
            return cls.from_dict(prompts, readonly=readonly)
        else:
            # If it's a directory, just use it as the location
            return cls(path, readonly=readonly)

    def add(
        self,
        content: str,
        name: Optional[str] = None,
        namespace: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        subset: Optional[str] = None,
    ) -> Prompt:
        """Add a new prompt to the store.

        Args:
            content: The content of the prompt
            name: Name of the prompt (optional, auto-generated if not provided)
            namespace: Namespace/organization (defaults to 'default')
            description: A description of the prompt
            tags: A list of tags for the prompt
            subset: Optional subset/variant name

        Returns:
            Prompt: The newly created prompt
        """
        if self.readonly:
            raise ReadOnlyStoreError("Cannot modify a read-only prompt store")

        # Set defaults
        if not namespace:
            namespace = "default"
        if not name:
            name = f"prompt-{str(uuid.uuid4())[:8]}"

        prompt_uuid = str(uuid.uuid4())
        now = datetime.now(timezone.utc).isoformat()

        prompt = Prompt(
            uuid=prompt_uuid,
            name=name,
            namespace=namespace,
            content=content,
            description=description,
            version=1,
            tags=tags or [],
            subset=subset,
            created_at=now,
            updated_at=now,
        )

        # Save to file system
        file_path = self._get_prompt_path(namespace, name, subset)
        self._write_prompt_file(file_path, prompt)

        # Update index
        index = self._load_index()
        index[prompt_uuid] = {
            "namespace": namespace,
            "name": name,
            "subset": subset,
            "path": str(file_path.relative_to(self.location)),
        }
        self._save_index(index)

        # Update aliases
        aliases = self._load_aliases()
        identifier = f"{namespace}/{name}"
        if subset:
            identifier += f"@{subset}"
        aliases[identifier] = prompt_uuid
        self._save_aliases(aliases)

        return prompt

    def get(
        self,
        identifier: str,
        version: Optional[int] = None,
        subset: Optional[str] = None,
    ) -> Prompt:
        """Retrieve a prompt by its identifier or UUID.

        Args:
            identifier: HuggingFace-style identifier (namespace/name[@subset]) or UUID
            version: The version of the prompt to retrieve
            subset: The subset of the prompt to retrieve (overrides identifier)

        Returns:
            Prompt: The prompt object
        """
        parsed = self._parse_identifier(identifier)

        # Handle UUID lookup (backward compatibility)
        if parsed["uuid"]:
            warnings.warn(
                "UUID-based lookup is deprecated. Use 'namespace/name' format.",
                DeprecationWarning,
                stacklevel=2,
            )
            return self._get_by_uuid(parsed["uuid"], version)

        # Use parsed values, but allow override by parameters
        namespace = parsed["namespace"]
        name = parsed["name"]
        subset = subset or parsed["subset"]

        # Get the file path and read the prompt
        file_path = self._get_prompt_path(namespace, name, subset, version)
        return self._read_prompt_file(file_path)

    def _get_by_uuid(self, uuid_val: str, version: Optional[int] = None) -> Prompt:
        """Get a prompt by UUID (backward compatibility)."""
        index = self._load_index()
        if uuid_val not in index:
            raise PromptNotFoundError(f"Prompt with UUID {uuid_val} not found")

        entry = index[uuid_val]
        namespace = entry["namespace"]
        name = entry["name"]
        subset = entry.get("subset")

        file_path = self._get_prompt_path(namespace, name, subset, version)
        return self._read_prompt_file(file_path)

    def find(self, query: str, field: str = "description") -> List[Prompt]:
        """Search for prompts based on a query.

        Args:
            query: The search query
            field: The field to search in (description, content, or tags)

        Returns:
            List[Prompt]: A list of prompts matching the query
        """
        if field not in ("description", "content", "tags"):
            raise ValueError("Field must be 'description', 'content', or 'tags'")

        prompts = []
        for prompt in self:
            if field == "tags":
                if any(query.lower() in tag.lower() for tag in prompt.tags):
                    prompts.append(prompt)
            elif field == "description" and prompt.description:
                if query.lower() in prompt.description.lower():
                    prompts.append(prompt)
            elif field == "content":
                if query.lower() in prompt.content.lower():
                    prompts.append(prompt)

        return prompts

    def update(
        self,
        identifier: str,
        content: str,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
    ) -> Prompt:
        """Update an existing prompt with a new version.

        Args:
            identifier: The identifier or UUID of the prompt to update
            content: New content for the prompt
            description: New description for the prompt
            tags: New tags for the prompt

        Returns:
            Prompt: The updated prompt

        Raises:
            ReadOnlyStoreError: If the store is read-only
            PromptNotFoundError: If the prompt doesn't exist
        """
        if self.readonly:
            raise ReadOnlyStoreError("Cannot modify a read-only prompt store")

        # Get the current prompt
        current = self.get(identifier)

        # Create new version
        now = datetime.now(timezone.utc).isoformat()
        new_version = current.version + 1

        # Save old version to versions directory if it's not already there
        if current.version > 0:
            old_version_path = self._get_prompt_path(
                current.namespace, current.name, current.subset, current.version
            )
            if not old_version_path.exists():
                self._write_prompt_file(old_version_path, current)

        # Create updated prompt
        updated = Prompt(
            uuid=current.uuid,
            name=current.name,
            namespace=current.namespace,
            content=content,
            description=description if description is not None else current.description,
            version=new_version,
            tags=tags if tags is not None else current.tags,
            subset=current.subset,
            created_at=current.created_at,
            updated_at=now,
        )

        # Write new version as the main prompt
        file_path = self._get_prompt_path(
            current.namespace, current.name, current.subset
        )
        self._write_prompt_file(file_path, updated)

        return updated

    def __iter__(self) -> Iterator[Prompt]:
        """Iterate over all prompts in the store."""
        index = self._load_index()
        for _, entry in index.items():
            try:
                file_path = self.location / entry["path"]
                yield self._read_prompt_file(file_path)
            except Exception as e:
                logger.warning(f"Failed to read prompt at {file_path}: {e}")
                # Skip prompts that can't be read
                continue

    def merge(self, other: "PromptStore", override: bool = False):
        """Merge another PromptStore into this one.

        Args:
            other: The other PromptStore to merge
            override: If True, override existing prompts with those from the other store

        Raises:
            ReadOnlyStoreError: If the store is read-only
        """
        if self.readonly:
            raise ReadOnlyStoreError("Cannot modify a read-only prompt store")

        for prompt in other:
            identifier = f"{prompt.namespace}/{prompt.name}"
            if prompt.subset:
                identifier += f"@{prompt.subset}"

            try:
                self.get(identifier)
                if override:
                    self.update(
                        identifier,
                        content=prompt.content,
                        description=prompt.description,
                        tags=prompt.tags,
                    )
            except PromptNotFoundError:
                # Doesn't exist, add it
                self.add(
                    content=prompt.content,
                    name=prompt.name,
                    namespace=prompt.namespace,
                    description=prompt.description,
                    tags=prompt.tags,
                    subset=prompt.subset,
                )

    def get_online(
        self, uuid: str, url: str, version: int | None = None, folder: str = "prompts"
    ) -> Prompt:
        """Retrieve a prompt by its UUID from an online store.

        Args:
            uuid: The UUID of the prompt to retrieve
            url: The URL of the online store
            version: The version of the prompt to retrieve
            folder: The folder name to use for caching (defaults to "prompts")

        Returns:
            Prompt: The prompt object

        Raises:
            PromptNotFoundError: If the prompt with the given UUID doesn't exist
        """
        data = pystow.ensure_json(folder, url=url)

        if uuid not in data:
            raise PromptNotFoundError(
                f"Prompt with UUID {uuid} not found in online store"
            )

        prompt_data = data[uuid]

        if version:
            version_data = next(
                (v for v in prompt_data["versions"] if v["version"] == version), None
            )
            if not version_data:
                raise PromptNotFoundError(
                    f"Version {version} of prompt {uuid} not found in online store"
                )
            content = version_data["content"]
            description = version_data["description"]
            timestamp = version_data["created_at"]
        else:
            content = prompt_data["content"]
            description = prompt_data["description"]
            timestamp = prompt_data["updated_at"]

        return Prompt(
            uuid=uuid,
            content=content,
            description=description,
            version=prompt_data["version"],
            tags=prompt_data["tags"],
            timestamp=timestamp,
        )

__init__(location, readonly=False)

Initialize a new PromptStore.

Parameters:

Name Type Description Default
location Union[str, Path]

Path to the directory where prompts will be stored

required
readonly bool

If True, the store will be read-only

False
Source code in src/promptstore/store.py
def __init__(self, location: Union[str, Path], readonly: bool = False):
    """Initialize a new PromptStore.

    Args:
        location: Path to the directory where prompts will be stored
        readonly: If True, the store will be read-only
    """
    self.location = Path(location)
    self.readonly = readonly
    self.promptstore_dir = self.location / ".promptstore"
    self.index_path = self.promptstore_dir / "index.json"
    self.aliases_path = self.promptstore_dir / "aliases.json"
    self._init_storage()

__iter__()

Iterate over all prompts in the store.

Source code in src/promptstore/store.py
def __iter__(self) -> Iterator[Prompt]:
    """Iterate over all prompts in the store."""
    index = self._load_index()
    for _, entry in index.items():
        try:
            file_path = self.location / entry["path"]
            yield self._read_prompt_file(file_path)
        except Exception as e:
            logger.warning(f"Failed to read prompt at {file_path}: {e}")
            # Skip prompts that can't be read
            continue

add(content, name=None, namespace=None, description=None, tags=None, subset=None)

Add a new prompt to the store.

Parameters:

Name Type Description Default
content str

The content of the prompt

required
name Optional[str]

Name of the prompt (optional, auto-generated if not provided)

None
namespace Optional[str]

Namespace/organization (defaults to 'default')

None
description Optional[str]

A description of the prompt

None
tags Optional[List[str]]

A list of tags for the prompt

None
subset Optional[str]

Optional subset/variant name

None

Returns:

Name Type Description
Prompt Prompt

The newly created prompt

Source code in src/promptstore/store.py
def add(
    self,
    content: str,
    name: Optional[str] = None,
    namespace: Optional[str] = None,
    description: Optional[str] = None,
    tags: Optional[List[str]] = None,
    subset: Optional[str] = None,
) -> Prompt:
    """Add a new prompt to the store.

    Args:
        content: The content of the prompt
        name: Name of the prompt (optional, auto-generated if not provided)
        namespace: Namespace/organization (defaults to 'default')
        description: A description of the prompt
        tags: A list of tags for the prompt
        subset: Optional subset/variant name

    Returns:
        Prompt: The newly created prompt
    """
    if self.readonly:
        raise ReadOnlyStoreError("Cannot modify a read-only prompt store")

    # Set defaults
    if not namespace:
        namespace = "default"
    if not name:
        name = f"prompt-{str(uuid.uuid4())[:8]}"

    prompt_uuid = str(uuid.uuid4())
    now = datetime.now(timezone.utc).isoformat()

    prompt = Prompt(
        uuid=prompt_uuid,
        name=name,
        namespace=namespace,
        content=content,
        description=description,
        version=1,
        tags=tags or [],
        subset=subset,
        created_at=now,
        updated_at=now,
    )

    # Save to file system
    file_path = self._get_prompt_path(namespace, name, subset)
    self._write_prompt_file(file_path, prompt)

    # Update index
    index = self._load_index()
    index[prompt_uuid] = {
        "namespace": namespace,
        "name": name,
        "subset": subset,
        "path": str(file_path.relative_to(self.location)),
    }
    self._save_index(index)

    # Update aliases
    aliases = self._load_aliases()
    identifier = f"{namespace}/{name}"
    if subset:
        identifier += f"@{subset}"
    aliases[identifier] = prompt_uuid
    self._save_aliases(aliases)

    return prompt

find(query, field='description')

Search for prompts based on a query.

Parameters:

Name Type Description Default
query str

The search query

required
field str

The field to search in (description, content, or tags)

'description'

Returns:

Type Description
List[Prompt]

List[Prompt]: A list of prompts matching the query

Source code in src/promptstore/store.py
def find(self, query: str, field: str = "description") -> List[Prompt]:
    """Search for prompts based on a query.

    Args:
        query: The search query
        field: The field to search in (description, content, or tags)

    Returns:
        List[Prompt]: A list of prompts matching the query
    """
    if field not in ("description", "content", "tags"):
        raise ValueError("Field must be 'description', 'content', or 'tags'")

    prompts = []
    for prompt in self:
        if field == "tags":
            if any(query.lower() in tag.lower() for tag in prompt.tags):
                prompts.append(prompt)
        elif field == "description" and prompt.description:
            if query.lower() in prompt.description.lower():
                prompts.append(prompt)
        elif field == "content":
            if query.lower() in prompt.content.lower():
                prompts.append(prompt)

    return prompts

from_dict(prompts, readonly=True) classmethod

Create a PromptStore from a dictionary.

Parameters:

Name Type Description Default
prompts Dict

Dictionary of prompt data (old JSON format)

required
readonly bool

If True, the store will be read-only

True

Returns:

Name Type Description
PromptStore PromptStore

A prompt store initialized with the given prompts

Source code in src/promptstore/store.py
@classmethod
def from_dict(cls, prompts: Dict, readonly: bool = True) -> "PromptStore":
    """Create a PromptStore from a dictionary.

    Args:
        prompts: Dictionary of prompt data (old JSON format)
        readonly: If True, the store will be read-only

    Returns:
        PromptStore: A prompt store initialized with the given prompts
    """
    import tempfile

    temp_dir = Path(tempfile.mkdtemp())
    # Create store as writable first to populate it
    store = cls(temp_dir, readonly=False)

    # Convert old format to new format
    for uuid_val, data in prompts.items():
        # Create a prompt with the old data
        namespace = data.get("namespace", "default")
        name = data.get("name", f"prompt-{uuid_val[:8]}")

        prompt = Prompt(
            uuid=uuid_val,
            name=name,
            namespace=namespace,
            content=data["content"],
            description=data.get("description"),
            version=data.get("version", 1),
            tags=data.get("tags", []),
            created_at=data.get("created_at"),
            updated_at=data.get("updated_at"),
        )

        # Save to file system
        file_path = store._get_prompt_path(namespace, name)
        store._write_prompt_file(file_path, prompt)

        # Update index
        index = store._load_index()
        index[uuid_val] = {
            "namespace": namespace,
            "name": name,
            "path": str(file_path.relative_to(store.location)),
        }
        store._save_index(index)

        # Update aliases
        aliases = store._load_aliases()
        aliases[f"{namespace}/{name}"] = uuid_val
        store._save_aliases(aliases)

    # Now set to readonly if requested
    store.readonly = readonly
    return store

from_file(path, readonly=True) classmethod

Create a PromptStore from a JSON file.

Parameters:

Name Type Description Default
path Union[str, Path]

Path to the JSON file containing prompts

required
readonly bool

If True, the store will be read-only

True

Returns:

Name Type Description
PromptStore PromptStore

A prompt store initialized with the prompts from the file

Source code in src/promptstore/store.py
@classmethod
def from_file(cls, path: Union[str, Path], readonly: bool = True) -> "PromptStore":
    """Create a PromptStore from a JSON file.

    Args:
        path: Path to the JSON file containing prompts
        readonly: If True, the store will be read-only

    Returns:
        PromptStore: A prompt store initialized with the prompts from the file
    """
    path = Path(path)
    if not path.exists():
        raise FileNotFoundError(f"Prompt file not found: {path}")

    if path.is_file():
        # If it's a single JSON file (old format)
        with open(path, "r", encoding="utf-8") as f:
            prompts = json.load(f)
        return cls.from_dict(prompts, readonly=readonly)
    else:
        # If it's a directory, just use it as the location
        return cls(path, readonly=readonly)

get(identifier, version=None, subset=None)

Retrieve a prompt by its identifier or UUID.

Parameters:

Name Type Description Default
identifier str

HuggingFace-style identifier (namespace/name[@subset]) or UUID

required
version Optional[int]

The version of the prompt to retrieve

None
subset Optional[str]

The subset of the prompt to retrieve (overrides identifier)

None

Returns:

Name Type Description
Prompt Prompt

The prompt object

Source code in src/promptstore/store.py
def get(
    self,
    identifier: str,
    version: Optional[int] = None,
    subset: Optional[str] = None,
) -> Prompt:
    """Retrieve a prompt by its identifier or UUID.

    Args:
        identifier: HuggingFace-style identifier (namespace/name[@subset]) or UUID
        version: The version of the prompt to retrieve
        subset: The subset of the prompt to retrieve (overrides identifier)

    Returns:
        Prompt: The prompt object
    """
    parsed = self._parse_identifier(identifier)

    # Handle UUID lookup (backward compatibility)
    if parsed["uuid"]:
        warnings.warn(
            "UUID-based lookup is deprecated. Use 'namespace/name' format.",
            DeprecationWarning,
            stacklevel=2,
        )
        return self._get_by_uuid(parsed["uuid"], version)

    # Use parsed values, but allow override by parameters
    namespace = parsed["namespace"]
    name = parsed["name"]
    subset = subset or parsed["subset"]

    # Get the file path and read the prompt
    file_path = self._get_prompt_path(namespace, name, subset, version)
    return self._read_prompt_file(file_path)

get_online(uuid, url, version=None, folder='prompts')

Retrieve a prompt by its UUID from an online store.

Parameters:

Name Type Description Default
uuid str

The UUID of the prompt to retrieve

required
url str

The URL of the online store

required
version int | None

The version of the prompt to retrieve

None
folder str

The folder name to use for caching (defaults to "prompts")

'prompts'

Returns:

Name Type Description
Prompt Prompt

The prompt object

Raises:

Type Description
PromptNotFoundError

If the prompt with the given UUID doesn't exist

Source code in src/promptstore/store.py
def get_online(
    self, uuid: str, url: str, version: int | None = None, folder: str = "prompts"
) -> Prompt:
    """Retrieve a prompt by its UUID from an online store.

    Args:
        uuid: The UUID of the prompt to retrieve
        url: The URL of the online store
        version: The version of the prompt to retrieve
        folder: The folder name to use for caching (defaults to "prompts")

    Returns:
        Prompt: The prompt object

    Raises:
        PromptNotFoundError: If the prompt with the given UUID doesn't exist
    """
    data = pystow.ensure_json(folder, url=url)

    if uuid not in data:
        raise PromptNotFoundError(
            f"Prompt with UUID {uuid} not found in online store"
        )

    prompt_data = data[uuid]

    if version:
        version_data = next(
            (v for v in prompt_data["versions"] if v["version"] == version), None
        )
        if not version_data:
            raise PromptNotFoundError(
                f"Version {version} of prompt {uuid} not found in online store"
            )
        content = version_data["content"]
        description = version_data["description"]
        timestamp = version_data["created_at"]
    else:
        content = prompt_data["content"]
        description = prompt_data["description"]
        timestamp = prompt_data["updated_at"]

    return Prompt(
        uuid=uuid,
        content=content,
        description=description,
        version=prompt_data["version"],
        tags=prompt_data["tags"],
        timestamp=timestamp,
    )

merge(other, override=False)

Merge another PromptStore into this one.

Parameters:

Name Type Description Default
other PromptStore

The other PromptStore to merge

required
override bool

If True, override existing prompts with those from the other store

False

Raises:

Type Description
ReadOnlyStoreError

If the store is read-only

Source code in src/promptstore/store.py
def merge(self, other: "PromptStore", override: bool = False):
    """Merge another PromptStore into this one.

    Args:
        other: The other PromptStore to merge
        override: If True, override existing prompts with those from the other store

    Raises:
        ReadOnlyStoreError: If the store is read-only
    """
    if self.readonly:
        raise ReadOnlyStoreError("Cannot modify a read-only prompt store")

    for prompt in other:
        identifier = f"{prompt.namespace}/{prompt.name}"
        if prompt.subset:
            identifier += f"@{prompt.subset}"

        try:
            self.get(identifier)
            if override:
                self.update(
                    identifier,
                    content=prompt.content,
                    description=prompt.description,
                    tags=prompt.tags,
                )
        except PromptNotFoundError:
            # Doesn't exist, add it
            self.add(
                content=prompt.content,
                name=prompt.name,
                namespace=prompt.namespace,
                description=prompt.description,
                tags=prompt.tags,
                subset=prompt.subset,
            )

update(identifier, content, description=None, tags=None)

Update an existing prompt with a new version.

Parameters:

Name Type Description Default
identifier str

The identifier or UUID of the prompt to update

required
content str

New content for the prompt

required
description Optional[str]

New description for the prompt

None
tags Optional[List[str]]

New tags for the prompt

None

Returns:

Name Type Description
Prompt Prompt

The updated prompt

Raises:

Type Description
ReadOnlyStoreError

If the store is read-only

PromptNotFoundError

If the prompt doesn't exist

Source code in src/promptstore/store.py
def update(
    self,
    identifier: str,
    content: str,
    description: Optional[str] = None,
    tags: Optional[List[str]] = None,
) -> Prompt:
    """Update an existing prompt with a new version.

    Args:
        identifier: The identifier or UUID of the prompt to update
        content: New content for the prompt
        description: New description for the prompt
        tags: New tags for the prompt

    Returns:
        Prompt: The updated prompt

    Raises:
        ReadOnlyStoreError: If the store is read-only
        PromptNotFoundError: If the prompt doesn't exist
    """
    if self.readonly:
        raise ReadOnlyStoreError("Cannot modify a read-only prompt store")

    # Get the current prompt
    current = self.get(identifier)

    # Create new version
    now = datetime.now(timezone.utc).isoformat()
    new_version = current.version + 1

    # Save old version to versions directory if it's not already there
    if current.version > 0:
        old_version_path = self._get_prompt_path(
            current.namespace, current.name, current.subset, current.version
        )
        if not old_version_path.exists():
            self._write_prompt_file(old_version_path, current)

    # Create updated prompt
    updated = Prompt(
        uuid=current.uuid,
        name=current.name,
        namespace=current.namespace,
        content=content,
        description=description if description is not None else current.description,
        version=new_version,
        tags=tags if tags is not None else current.tags,
        subset=current.subset,
        created_at=current.created_at,
        updated_at=now,
    )

    # Write new version as the main prompt
    file_path = self._get_prompt_path(
        current.namespace, current.name, current.subset
    )
    self._write_prompt_file(file_path, updated)

    return updated

Prompt

promptstore.prompt.Prompt

Source code in src/promptstore/prompt.py
class Prompt:
    def __init__(
        self,
        content: str,
        version: int,
        name: Optional[str] = None,
        namespace: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        subset: Optional[str] = None,
        timestamp: Optional[str] = None,
        uuid: Optional[str] = None,
        created_at: Optional[str] = None,
        updated_at: Optional[str] = None,
    ):
        self.uuid = uuid or str(uuid_module.uuid4())
        self.name = name
        self.namespace = namespace
        self.content = content
        self.description = description
        self.version = version
        self.tags = tags or []
        self.subset = subset
        self.timestamp = timestamp
        self.created_at = created_at
        self.updated_at = updated_at
        try:
            self._template = Template(content)
        except Exception as e:
            raise ValueError(f"Invalid Jinja2 template: {e}") from e
        self.variables = self._extract_variables(content)

    @property
    def identifier(self) -> str:
        """Get HuggingFace-style identifier."""
        if not self.namespace or not self.name:
            return self.uuid

        base = f"{self.namespace}/{self.name}"
        if self.subset:
            base += f"@{self.subset}"
        return base

    def fill(self, variables: dict) -> str:
        """Fill the prompt template with provided variables."""
        return self._template.render(**variables)

    def _extract_variables(self, content: str) -> List[str]:
        """Extract variable names from the template content."""
        env = Environment()
        ast = env.parse(content)
        variables = meta.find_undeclared_variables(ast)
        return sorted(list(variables))  # Convert set to sorted list

    def get_variables(self) -> List[str]:
        """Return the list of variable names in the template."""
        return self.variables

    def _highlight_variables(self, content: str) -> str:
        """Highlight Jinja2 variables in markdown format."""
        pattern = r"(\{\{[^}]+\}\})"
        return re.sub(pattern, r"**`\1`**", content)

    @staticmethod
    def _unhighlight_variables(content: str) -> str:
        """Remove markdown highlighting from Jinja2 variables."""
        pattern = r"\*\*`(\{\{[^}]+\}\})`\*\*"
        return re.sub(pattern, r"\1", content)

    def to_markdown(self) -> str:
        """Export prompt as Markdown with YAML frontmatter."""
        frontmatter = {
            "uuid": self.uuid,
            "name": self.name,
            "namespace": self.namespace,
            "description": self.description,
            "version": self.version,
            "tags": self.tags,
            "variables": self.variables,
        }

        if self.subset:
            frontmatter["subset"] = self.subset
        if self.created_at:
            frontmatter["created_at"] = self.created_at
        if self.updated_at:
            frontmatter["updated_at"] = self.updated_at

        # Remove None values
        frontmatter = {k: v for k, v in frontmatter.items() if v is not None}

        yaml_str = yaml.dump(frontmatter, default_flow_style=False, sort_keys=False)

        highlighted_content = self._highlight_variables(self.content)

        return f"---\n{yaml_str}---\n\n{highlighted_content}"

    @classmethod
    def from_markdown(cls, markdown_content: str) -> "Prompt":
        """Create prompt from Markdown with YAML frontmatter."""
        if not markdown_content.startswith("---"):
            raise ValueError("Markdown content must start with YAML frontmatter")

        parts = markdown_content.split("---", 2)
        if len(parts) < 3:
            raise ValueError("Invalid frontmatter format")

        frontmatter = yaml.safe_load(parts[1])
        if frontmatter is None:
            raise ValueError("Empty frontmatter")
        content = parts[2].strip()

        content = cls._unhighlight_variables(content)

        return cls(
            uuid=frontmatter.get("uuid"),
            name=frontmatter.get("name"),
            namespace=frontmatter.get("namespace"),
            content=content,
            description=frontmatter.get("description"),
            version=frontmatter.get("version", 1),
            tags=frontmatter.get("tags", []),
            subset=frontmatter.get("subset"),
            created_at=frontmatter.get("created_at"),
            updated_at=frontmatter.get("updated_at"),
        )

identifier property

Get HuggingFace-style identifier.

fill(variables)

Fill the prompt template with provided variables.

Source code in src/promptstore/prompt.py
def fill(self, variables: dict) -> str:
    """Fill the prompt template with provided variables."""
    return self._template.render(**variables)

from_markdown(markdown_content) classmethod

Create prompt from Markdown with YAML frontmatter.

Source code in src/promptstore/prompt.py
@classmethod
def from_markdown(cls, markdown_content: str) -> "Prompt":
    """Create prompt from Markdown with YAML frontmatter."""
    if not markdown_content.startswith("---"):
        raise ValueError("Markdown content must start with YAML frontmatter")

    parts = markdown_content.split("---", 2)
    if len(parts) < 3:
        raise ValueError("Invalid frontmatter format")

    frontmatter = yaml.safe_load(parts[1])
    if frontmatter is None:
        raise ValueError("Empty frontmatter")
    content = parts[2].strip()

    content = cls._unhighlight_variables(content)

    return cls(
        uuid=frontmatter.get("uuid"),
        name=frontmatter.get("name"),
        namespace=frontmatter.get("namespace"),
        content=content,
        description=frontmatter.get("description"),
        version=frontmatter.get("version", 1),
        tags=frontmatter.get("tags", []),
        subset=frontmatter.get("subset"),
        created_at=frontmatter.get("created_at"),
        updated_at=frontmatter.get("updated_at"),
    )

get_variables()

Return the list of variable names in the template.

Source code in src/promptstore/prompt.py
def get_variables(self) -> List[str]:
    """Return the list of variable names in the template."""
    return self.variables

to_markdown()

Export prompt as Markdown with YAML frontmatter.

Source code in src/promptstore/prompt.py
def to_markdown(self) -> str:
    """Export prompt as Markdown with YAML frontmatter."""
    frontmatter = {
        "uuid": self.uuid,
        "name": self.name,
        "namespace": self.namespace,
        "description": self.description,
        "version": self.version,
        "tags": self.tags,
        "variables": self.variables,
    }

    if self.subset:
        frontmatter["subset"] = self.subset
    if self.created_at:
        frontmatter["created_at"] = self.created_at
    if self.updated_at:
        frontmatter["updated_at"] = self.updated_at

    # Remove None values
    frontmatter = {k: v for k, v in frontmatter.items() if v is not None}

    yaml_str = yaml.dump(frontmatter, default_flow_style=False, sort_keys=False)

    highlighted_content = self._highlight_variables(self.content)

    return f"---\n{yaml_str}---\n\n{highlighted_content}"

Exceptions

promptstore.exceptions

PromptNotFoundError

Bases: Exception

Raised when a prompt with the given UUID is not found.

Source code in src/promptstore/exceptions.py
1
2
3
4
class PromptNotFoundError(Exception):
    """Raised when a prompt with the given UUID is not found."""

    pass

ReadOnlyStoreError

Bases: Exception

Raised when attempting to modify a read-only store.

Source code in src/promptstore/exceptions.py
class ReadOnlyStoreError(Exception):
    """Raised when attempting to modify a read-only store."""

    pass