Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat & bugfix: Added metadata features and fixed naming bug #11

Merged
merged 17 commits into from
Sep 7, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions src/banks/registries/directory.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
# SPDX-FileCopyrightText: 2023-present Massimiliano Pippi <[email protected]>
#
# SPDX-License-Identifier: MIT
import json
from pathlib import Path

from pydantic import BaseModel, Field

from banks import Prompt
from banks.registry import TemplateNotFoundError

# Constants
default_version = "0"
default_index_name = "index.json"
default_meta_path = "meta"


class PromptFile(BaseModel):
name: str
Expand All @@ -26,11 +32,12 @@ def __init__(self, directory_path: Path, *, force_reindex: bool = False):
raise ValueError(msg)

self._path = directory_path
self._index_path = self._path / "index.json"
self._index_path = self._path / default_index_name
if not self._index_path.exists() or force_reindex:
self._scan()
else:
self._load()
self._meta_path = self._path / default_meta_path

def _load(self):
self._index = PromptFileIndex.model_validate_json(self._index_path.read_text())
Expand All @@ -43,19 +50,42 @@ def _scan(self):
self._index_path.write_text(self._index.model_dump_json())

def get(self, *, name: str, version: str | None = None) -> "Prompt":
version = version or "0"
version = version or default_version
for pf in self._index.files:
if pf.name == name and pf.version == version and pf.path.exists():
return Prompt(pf.path.read_text())
raise TemplateNotFoundError

def set(self, *, name: str, prompt: Prompt, version: str | None = None, overwrite: bool = False):
version = version or "0"
version = version or default_version
for pf in self._index.files:
if pf.name == name and pf.version == version and overwrite:
pf.path.write_text(prompt.raw)
return
new_prompt_file = self._path / "{name}.{version}.jinja"
new_prompt_file = self._path / f"{name}.{version}.jinja"
new_prompt_file.write_text(prompt.raw)
pf = PromptFile(name=name, version=version, path=new_prompt_file)
self._index.files.append(pf)

def get_meta(self, *, name: str, version: str | None = None) -> dict:
version = version or default_version
meta_path = self._meta_path / f"{name}.{version}.json"
if not meta_path.exists():
msg = f"Meta directory or file for prompt {name}:{version}.jinja not found."
raise FileNotFoundError(msg)
return json.loads(open(meta_path).read())

def set_meta(self, *, meta: dict, name: str, version: str | None = None, overwrite: bool = False):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would try to pass meta to the regular set() (see comment above)

Copy link
Collaborator Author

@mayankjobanputra mayankjobanputra Sep 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would do that but I will also provide set_meta separately because there are many use cases (i.e., adding accuracy, adding failed stats) that require setting meta after running the prompt. We also need such things per prompt so I will still keep the different folders for storing the meta and the prompt/index.

version = version or default_version
if not self._meta_path.exists():
self._meta_path.mkdir()
if Path(self._path / f"{name}.{version}.jinja") not in [pf.path for pf in self._index.files]:
msg = f"Prompt {name}.{version}.jinja not found in the index. Cannot set meta for a non-existing prompt."
raise ValueError(msg)

if f"{name}:{version}.json" in self._meta_path.glob("*.json"):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

glob returns a generator so this should be something like

if f"{name}:{version}.json" in list(self._meta_path.glob("*.json"))

is it working?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, it wasn't. I fixed and added unit tests as well. :)
Thanks for catching this one 🙌

if not overwrite:
msg = f"Meta file for prompt {name}:{version} already exists. Use overwrite=True to overwrite."
raise ValueError(msg)
meta_path = self._meta_path / f"{name}.{version}.json"
meta_path.write_text(json.dumps(meta))
29 changes: 28 additions & 1 deletion tests/test_directory_registry.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import time
from pathlib import Path

import pytest
Expand Down Expand Up @@ -32,7 +33,7 @@ def test_init_from_existing_index(populated_dir):


def test_init_from_existing_index_force(populated_dir):
r = DirectoryTemplateRegistry(populated_dir) # creates the index
_ = DirectoryTemplateRegistry(populated_dir) # creates the index
# change the directory structure
f = populated_dir / "blog.jinja"
os.remove(f)
Expand Down Expand Up @@ -65,3 +66,29 @@ def test_set_existing_overwrite(populated_dir):
new_prompt = Prompt("a new prompt!")
r.set(name="blog", prompt=new_prompt, overwrite=True)
assert r.get(name="blog").raw.startswith("a new prompt!")


def test_set_multiple_templates(populated_dir):
r = DirectoryTemplateRegistry(Path(populated_dir))
new_prompt = Prompt("a very new prompt!")
old_prompt = Prompt("an old prompt!")
r.set(name="new", version="2", prompt=new_prompt)
r.set(name="old", version="1", prompt=old_prompt)
assert r.get(name="old", version="1").raw == "an old prompt!"
assert r.get(name="new", version="2").raw == "a very new prompt!"


def test_empty_meta(populated_dir):
r = DirectoryTemplateRegistry(populated_dir)
with pytest.raises(FileNotFoundError):
r.get_meta(name="blog")


def test_set_meta(populated_dir):
r = DirectoryTemplateRegistry(populated_dir)
new_prompt = Prompt("a very new prompt!")
r.set(name="new", version="3", prompt=new_prompt)
r.set_meta(name="new", version="3", meta={"accuracy": 91.2, "last_updated": time.ctime()})
assert r.get_meta(name="new", version="3") == {"accuracy": 91.2, "last_updated": time.ctime()}
with pytest.raises(ValueError):
r.set_meta(name="foo", version="bar", meta={"accuracy": 91.2, "last_updated": time.ctime()}, overwrite=False)
Loading