Skip to content

Commit c6ad396

Browse files
committed
chore: Add prompt property and its associated tests
1 parent 0027e40 commit c6ad396

6 files changed

Lines changed: 169 additions & 2 deletions

File tree

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
OPENAI_API_KEY=your-api-key-here

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.venv
22
__pycache__
33
*.egg-info
4+
.env

example/structure.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ structure:
1010
echo "Hello, {author_name}!"
1111
- LICENSE:
1212
file: https://raw.githubusercontent.com/nishanths/license/master/LICENSE
13+
- .gitignore:
14+
prompt: |
15+
Generate the content for a .gitignore file that is appropriate for a Python project. Include common directories and file types that should be ignored.

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
PyYAML
22
requests
3+
openai
4+
python-dotenv

struct_module/main.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,50 @@
44
import logging
55
from string import Template
66
import time
7+
import yaml
8+
import openai
9+
from dotenv import load_dotenv
10+
11+
load_dotenv()
12+
13+
openai_api_key = os.getenv("OPENAI_API_KEY")
14+
15+
if not openai_api_key:
16+
raise ValueError("OpenAI API key not found. Please set it in the .env file.")
17+
18+
openai.api_key = openai_api_key
719

820
class FileItem:
921
def __init__(self, properties):
1022
self.name = properties.get("name")
1123
self.content = properties.get("content")
1224
self.remote_location = properties.get("file")
1325
self.permissions = properties.get("permissions")
26+
self.prompt = properties.get("prompt")
27+
28+
def process_prompt(self):
29+
if self.prompt:
30+
logging.debug(f"Processing prompt: {self.prompt}")
31+
response = openai.Completion.create(
32+
engine="davinci",
33+
prompt=self.prompt,
34+
max_tokens=100
35+
)
36+
self.content = response.choices[0].text
37+
logging.debug(f"Generated content: {self.content}")
1438

1539
def fetch_content(self):
1640
if self.remote_location:
41+
logging.debug(f"Fetching content from: {self.remote_location}")
1742
response = requests.get(self.remote_location)
43+
logging.debug(f"Response status code: {response.status_code}")
1844
response.raise_for_status()
1945
self.content = response.text
46+
logging.debug(f"Fetched content: {self.content}")
2047

2148
def apply_template_variables(self, template_vars):
2249
if self.content and template_vars:
50+
logging.debug(f"Applying template variables: {template_vars}")
2351
template = Template(self.content)
2452
self.content = template.substitute(template_vars)
2553

@@ -65,12 +93,21 @@ def validate_configuration(structure):
6593
if not isinstance(name, str):
6694
raise ValueError("Each name in the 'structure' item must be a string.")
6795
if isinstance(content, dict):
68-
if 'content' not in content and 'file' not in content:
69-
raise ValueError(f"Dictionary item '{name}' must contain either 'content' or 'file' key.")
96+
# Check that any of the keys 'content', 'file' or 'prompt' is present
97+
if 'content' not in content and 'file' not in content and 'prompt' not in content:
98+
raise ValueError(f"Dictionary item '{name}' must contain either 'content' or 'file' or 'prompt' key.")
99+
# Check if 'file' key is present and its value is a string
70100
if 'file' in content and not isinstance(content['file'], str):
71101
raise ValueError(f"The 'file' value for '{name}' must be a string.")
102+
# Check if 'permissions' key is present and its value is a string
72103
if 'permissions' in content and not isinstance(content['permissions'], str):
73104
raise ValueError(f"The 'permissions' value for '{name}' must be a string.")
105+
# Check if 'prompt' key is present and its value is a string
106+
if 'prompt' in content and not isinstance(content['prompt'], str):
107+
raise ValueError(f"The 'prompt' value for '{name}' must be a string.")
108+
# Check if 'prompt' key is present but no OpenAI API key is found
109+
if 'prompt' in content and not openai_api_key:
110+
raise ValueError("Using prompt property and no OpenAI API key was found. Please set it in the .env file.")
74111
elif not isinstance(content, str):
75112
raise ValueError(f"The content of '{name}' must be a string or dictionary.")
76113
logging.info("Configuration validation passed.")
@@ -88,6 +125,7 @@ def create_structure(base_path, structure, dry_run=False, template_vars=None, ba
88125
file_item = FileItem({"name": name, "content": content})
89126

90127
file_item.apply_template_variables(template_vars)
128+
file_item.process_prompt()
91129
file_item.create(base_path, dry_run, backup_path, file_strategy)
92130

93131
def main():

tests/test_prompt.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import pytest
2+
import os
3+
import tempfile
4+
import requests
5+
import logging
6+
from unittest.mock import patch, MagicMock
7+
from struct_module.main import FileItem, validate_configuration, create_structure
8+
9+
# Mock the OpenAI API response
10+
@patch('struct_module.openai.Completion.create')
11+
def test_process_prompt(mock_openai_create):
12+
mock_openai_create.return_value = MagicMock(choices=[MagicMock(text='Generated content from prompt')])
13+
14+
file_item = FileItem({"name": "generated_file.txt", "prompt": "Write a short story about a dragon."})
15+
file_item.process_prompt()
16+
17+
assert file_item.content == 'Generated content from prompt'
18+
mock_openai_create.assert_called_once_with(
19+
engine="davinci",
20+
prompt="Write a short story about a dragon.",
21+
max_tokens=100
22+
)
23+
24+
# Test for validate_configuration with prompt
25+
def test_validate_configuration_with_prompt():
26+
valid_structure = [
27+
{
28+
"story.txt": {
29+
"prompt": "Write a short story about a dragon."
30+
}
31+
}
32+
]
33+
invalid_structure = [
34+
{
35+
"story.txt": {
36+
"prompt": 12345 # Invalid type
37+
}
38+
}
39+
]
40+
41+
validate_configuration(valid_structure)
42+
43+
with pytest.raises(ValueError):
44+
validate_configuration(invalid_structure)
45+
46+
# Test for creating a file with prompt content
47+
@patch('struct_module.openai.Completion.create')
48+
def test_create_structure_with_prompt(mock_openai_create):
49+
mock_openai_create.return_value = MagicMock(choices=[MagicMock(text='Generated content from prompt')])
50+
51+
structure = [
52+
{
53+
"story.txt": {
54+
"prompt": "Write a short story about a dragon."
55+
}
56+
}
57+
]
58+
59+
with tempfile.TemporaryDirectory() as tmpdirname:
60+
create_structure(tmpdirname, structure)
61+
62+
story_path = os.path.join(tmpdirname, "story.txt")
63+
assert os.path.exists(story_path)
64+
with open(story_path, 'r') as f:
65+
assert f.read() == 'Generated content from prompt'
66+
67+
# Test for dry run with prompt
68+
@patch('struct_module.openai.Completion.create')
69+
def test_dry_run_with_prompt(mock_openai_create, caplog):
70+
mock_openai_create.return_value = MagicMock(choices=[MagicMock(text='Generated content from prompt')])
71+
72+
structure = [
73+
{
74+
"story.txt": {
75+
"prompt": "Write a short story about a dragon."
76+
}
77+
}
78+
]
79+
80+
with tempfile.TemporaryDirectory() as tmpdirname:
81+
with caplog.at_level(logging.INFO):
82+
create_structure(tmpdirname, structure, dry_run=True)
83+
84+
assert not os.path.exists(os.path.join(tmpdirname, "story.txt"))
85+
assert any("[DRY RUN] Would create file:" in message for message in caplog.messages)
86+
assert any("Generated content from prompt" in message for message in caplog.messages)
87+
88+
# Mocking requests.get for testing fetch_remote_content within create_structure with prompt
89+
@patch('struct_module.requests.get')
90+
@patch('struct_module.openai.Completion.create')
91+
def test_create_structure_with_remote_content_and_prompt(mock_openai_create, mock_get):
92+
mock_get.return_value.status_code = 200
93+
mock_get.return_value.text = "Remote content"
94+
mock_openai_create.return_value = MagicMock(choices=[MagicMock(text='Generated content from prompt')])
95+
96+
structure = [
97+
{
98+
"LICENSE": {
99+
"file": "https://example.com/mock"
100+
}
101+
},
102+
{
103+
"story.txt": {
104+
"prompt": "Write a short story about a dragon."
105+
}
106+
}
107+
]
108+
109+
with tempfile.TemporaryDirectory() as tmpdirname:
110+
create_structure(tmpdirname, structure)
111+
112+
license_path = os.path.join(tmpdirname, "LICENSE")
113+
story_path = os.path.join(tmpdirname, "story.txt")
114+
115+
assert os.path.exists(license_path)
116+
assert os.path.exists(story_path)
117+
118+
with open(license_path, 'r') as f:
119+
assert f.read() == "Remote content"
120+
121+
with open(story_path, 'r') as f:
122+
assert f.read() == 'Generated content from prompt'

0 commit comments

Comments
 (0)