mirror of
https://github.com/NovaOSS/luna.git
synced 2024-11-25 12:43:59 +01:00
now open source
This commit is contained in:
parent
5027fb71c6
commit
624413bf33
22
.vscode/settings.json
vendored
Normal file
22
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/Thumbs.db": true,
|
||||
".vscode": true,
|
||||
"luna/__pycache__": true,
|
||||
"luna/training/clock/clock/__pycache__": true,
|
||||
"luna/training/clock/__pycache__": true,
|
||||
"luna/training/getip/__pycache__": true
|
||||
},
|
||||
"hide-files.files": [
|
||||
".vscode",
|
||||
"luna/__pycache__",
|
||||
"luna/training/clock/clock/__pycache__",
|
||||
"luna/training/clock/__pycache__",
|
||||
"luna/training/getip/__pycache__"
|
||||
]
|
||||
}
|
2
luna/__main__.py
Normal file
2
luna/__main__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
import main
|
||||
main.main()
|
23
luna/ai.py
Normal file
23
luna/ai.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
"""Main module for Luna."""
|
||||
|
||||
import os
|
||||
import openai
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
if os.getenv('OPENAI_API_BASE'):
|
||||
openai.api_base = os.getenv('OPENAI_API_BASE')
|
||||
openai.api_key = os.getenv('OPENAI_API_KEY')
|
||||
|
||||
MODEL = os.getenv('OPENAI_CHAT_MODEL') or 'gpt-3.5-turbo'
|
||||
|
||||
def generate(messages: str):
|
||||
"""Generates a new message based on the given messages."""
|
||||
|
||||
return openai.ChatCompletion.create(
|
||||
model=MODEL,
|
||||
messages=messages,
|
||||
temperature=0.2
|
||||
).choices[0].message.content
|
64
luna/main.py
Normal file
64
luna/main.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
import ai
|
||||
import os
|
||||
import prompts
|
||||
import testing
|
||||
|
||||
from rich import print
|
||||
from rich.progress import track
|
||||
from rich.console import Console
|
||||
|
||||
OUTPUT_PATH = r'C:\Users\Lynx\Desktop\luna_outp'
|
||||
|
||||
console = Console(record=True)
|
||||
|
||||
print('[light_steel_blue]Welcome to [bold]L u n a[/] 💜')
|
||||
|
||||
def main():
|
||||
"""Asks for a project description and generate the project."""
|
||||
|
||||
try:
|
||||
prompt = console.input("[orchid1 bold]What kind of project should I create for you? 💬[/] ")
|
||||
except KeyboardInterrupt:
|
||||
console.print('\n[orange1 bold]Bye! 👋[/]')
|
||||
return
|
||||
|
||||
console.print('[yellow2]This might take a while, so go ahead and enjoy a cup of tea. ☕[/]')
|
||||
|
||||
messages = prompts.generate_messages()
|
||||
messages.append({
|
||||
'role': 'user',
|
||||
'content': f'[FILE-STRUCTURE]: {prompt}'
|
||||
})
|
||||
|
||||
file_structure = ai.generate(messages).strip()
|
||||
file_list = ''.join(['\t' + path for path in file_structure])
|
||||
console.print(f'[turquoise2 bold]File structure:[/]\n{file_list}')
|
||||
|
||||
messages.append({
|
||||
'role': 'assistant',
|
||||
'content': file_structure
|
||||
})
|
||||
|
||||
for file_path in track(file_structure.splitlines(), description='[light_steel_blue]Generating files...'):
|
||||
messages.append({
|
||||
'role': 'user',
|
||||
'content': f'[FILE-CONTENT]: {file_path}'
|
||||
})
|
||||
|
||||
file_content = ai.generate(messages)
|
||||
|
||||
messages.append({
|
||||
'role': 'assistant',
|
||||
'content': file_content
|
||||
})
|
||||
|
||||
path = os.path.join(OUTPUT_PATH, file_path).replace('\\', '/')
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
|
||||
with open(path, 'w', encoding='utf8') as file_:
|
||||
file_.write(file_content)
|
||||
|
||||
console.print('[sea_green1 bold]Done! ✅[/]')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
110
luna/prompts.json
Normal file
110
luna/prompts.json
Normal file
|
@ -0,0 +1,110 @@
|
|||
[
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You program Python projects by providing every file's\ncontents, a documentation in the form of a Markdown file as well as a list of all files.\nIt is really important that also tests are provided, which check the functionality of the\nproject! The tests should check the functionality of the project, and should be created at the end.\nThe first file content to provide is the README.md file, which should contain a description of the\nproject, as well as a simple documentation on how to run the project, as well as how it works.\n\nFor example, for a web app, you should not only provide the commands to run the project, but also\nthe URL paths and what they do.\n\nKeep in mind to also use a nice CSS design if the project is a web app!\nCompletely avoid using APIs which require an API key, as this would make it impossible to run the project!\nWhenever possible, use APIs which are free to use and don't require an API key instead!\n"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[FILE-STRUCTURE]: A simple website called SimpleClock. It should display the current time on /."
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "main.py\nREADME.md\ntests.py\nsimpleclock/__init__.py\nsimpleclock/static/index.js\nsimpleclock/static/style.css\nsimpleclock/templates/base.html\nsimpleclock/templates/index.html\n"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[FILE-CONTENT]: main.py"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "\"\"\"Web server start module.\"\"\"\n\nimport sys\n\nfrom clock import create_app\n\ndef run(port=sys.argv[1] if len(sys.argv) > 1 else 5000):\n \"\"\"Starts the web server.\"\"\"\n create_app().run(port=port)\n\nif __name__ == '__main__':\n run()\n"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[FILE-CONTENT]: README.md"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "# SimpleClock\nA simple web app which displays the current time.\n\n## Get started\nTo get started, follow these steps:\n\n1. Run the app using\n```bash\npython main.py\n```\n2. Open the app in your browser at http://localhost:5000\n3. Enjoy!"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[FILE-CONTENT]: tests.py"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "\"\"\"LUNA Tests.\"\"\"\n\nimport time\nimport requests\nimport multiprocessing\n\nclass Tester:\n \"\"\"Tests if the program is working as expected.\"\"\"\n\n def __init__(self):\n \"\"\"Runs the tests.\"\"\"\n # pylint: disable=import-outside-toplevel\n import main\n\n self.server = multiprocessing.Process(target=main.run, args=(5000,))\n self.server.start()\n\n time.sleep(1)\n print('Server started!')\n\n resp = requests.get('http://localhost:5000/', timeout=5)\n assert 'The current time is:' in resp.text\n\n def stop(self):\n \"\"\"Runs after the tests are done, whether they have been successful or not.\"\"\"\n self.server.terminate()\n"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[FILE-CONTENT]: simpleclock/__init__.py"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "import flask\n\ndef create_app():\n \"\"\"Returns the Flask app.\"\"\"\n\n app = flask.Flask(__name__)\n\n @app.route('/')\n def index():\n \"\"\"Display the current time.\"\"\"\n return flask.render_template('index.html')\n\n return app\n"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[FILE-CONTENT]: simpleclock/static/index.js"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "// use momentjs to display the time, nicely formatted.\n// updates every second\n\nfunction update() {\n time.innerHTML = moment().format('h:mm:ss a');\n }\n\nupdate();\nsetInterval(update, 1000);\n"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[FILE-CONTENT]: simpleclock/static/style.css"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "body {\n font-size: 1.2rem;\n scroll-behavior: smooth;\n}\n\n* {\n box-sizing: border-box;\n font-family: sans-serif;\n}\n\nmain {\n max-width: 800px;\n margin: 0 auto;\n padding: 1rem;\n}\n\nh1 {\n font-size: 2rem;\n}\n\np {\n font-size: 1.2rem;\n}\n\n#time {\n font-weight: bold;\n}\n"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[FILE-CONTENT]: simpleclock/templates/base.html"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "<!DOCTYPE html>\n<html>\n<head>\n <title>SimpleClock</title>\n <link rel=\"stylesheet\" href=\"{{ url_for('static', filename='style.css') }}\">\n</head>\n<body>\n <main>\n {% block content %}\n {% endblock %}\n </main>\n <script src=\"https://cdn.jsdelivr.net/npm/moment/moment.min.js\"></script>\n <script src=\"{{ url_for('static', filename='index.js') }}\"></script>\n</body>\n</html>"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[FILE-CONTENT]: simpleclock/templates/index.html"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "{% extends 'base.html' %}\n{% block content %}\n <h1>SimpleClock</h1>\n <p>Welcome to this website!</p>\n <p>The current time is: <span id=\"time\"></span>.</p>\n{% endblock %}"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[FILE-STRUCTURE]: A very simple CLI which returns the user's IP address."
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "cli.py\nREADME.md\ntests.py\n"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[FILE-CONTENT]: cli.py"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "\"\"\"Get the IP address of the client.\"\"\"\n\nimport typer\nimport requests\n\napp = typer.Typer()\n\n@app.command()\ndef main():\n \"\"\"Shows the IP address of the client.\"\"\"\n print(requests.get('https://checkip.amazonaws.com', timeout=5).text.strip())\n\nif __name__ == '__main__':\n typer.run(main)\n"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[FILE-CONTENT]: README.md"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "# `GetIP`\nA very simple CLI app that displays the user's IP address.\n\n## Get started\nIt's very simple to run this app. Just run the following command in the terminal:\n\n```bash\npython cli.py\n```\n\nExample output:\n```\n123.123.34.34\n```\n"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[FILE-CONTENT]: tests.py"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "\"\"\"LUNA Tests.\"\"\"\n\nimport os\nimport requests\nimport subprocess\n\nclass Tester:\n \"\"\"Tests if the program is working as expected.\"\"\"\n\n def __init__(self):\n \"\"\"Runs the tests.\"\"\"\n\n # check the print output of cli.app.main()\n os.chdir(os.path.dirname(__file__))\n output = subprocess.check_output(['python', 'cli.py']).decode('utf8')\n\n ip_addr = requests.get('https://checkip.amazonaws.com', timeout=5).text.strip()\n assert ip_addr in output\n\n def stop(self):\n \"\"\"Runs after the tests are done, whether they have been successful or not.\"\"\"\n # no need to stop anything\n"
|
||||
}
|
||||
]
|
76
luna/prompts.py
Normal file
76
luna/prompts.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
"""The prompt module contains the demo "conversation" messages used to provide an example for the
|
||||
AI of how to respond to the user."""
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
messages = [
|
||||
{'role': 'system', 'content': """You program Python projects by providing every file's
|
||||
contents, a documentation in the form of a Markdown file as well as a list of all files.
|
||||
|
||||
It is really important that also tests are provided, which check the functionality of the
|
||||
project! The tests should check the functionality of the project, and should be created at the end.
|
||||
The first file content to provide is the README.md file, which should contain a description of the
|
||||
project, as well as a simple documentation on how to run the project, as well as how it works.
|
||||
|
||||
For example, for a web app, you should not only provide the commands to run the project, but also
|
||||
the URL paths and what they do.
|
||||
|
||||
Keep in mind to also use a nice CSS design if the project is a web app!
|
||||
Completely avoid using APIs which require an API key, as this would make it impossible to run the project!
|
||||
Whenever possible, use APIs which are free to use and don't require an API key instead!
|
||||
"""}
|
||||
]
|
||||
|
||||
def generate_messages():
|
||||
"""Generates the messages for the AI "conversation".
|
||||
"""
|
||||
|
||||
for example in os.listdir('luna/training'):
|
||||
example_path = f'luna/training/{example}'
|
||||
|
||||
with open(f'{example_path}/_prompt.luna.txt', encoding='utf8') as prompt_file:
|
||||
prompt = prompt_file.read()
|
||||
|
||||
messages.append({
|
||||
'role': 'user',
|
||||
'content': f'[FILE-STRUCTURE]: {prompt}'
|
||||
})
|
||||
|
||||
contents = {}
|
||||
|
||||
for root, _, files in os.walk(example_path):
|
||||
for file_ in files:
|
||||
file_path = os.path.join(root, file_)\
|
||||
.replace('\\', '/').replace(example_path + '/', '')
|
||||
|
||||
if '__pycache__' in file_path or '.luna.' in file_path:
|
||||
continue
|
||||
|
||||
contents[file_path] = open(os.path.join(root, file_), encoding='utf8').read()
|
||||
|
||||
structure = ''
|
||||
|
||||
for file_path in contents:
|
||||
structure += f'{file_path}\n'
|
||||
|
||||
messages.append({
|
||||
'role': 'assistant',
|
||||
'content': structure
|
||||
})
|
||||
|
||||
for file_path, content in contents.items():
|
||||
messages.append({
|
||||
'role': 'user',
|
||||
'content': f'[FILE-CONTENT]: {file_path}'
|
||||
})
|
||||
|
||||
messages.append({
|
||||
'role': 'assistant',
|
||||
'content': content
|
||||
})
|
||||
|
||||
return messages
|
||||
|
||||
if __name__ == '__main__':
|
||||
json.dump(generate_messages(), open('luna/prompts.json', 'w', encoding='utf8'), indent=4)
|
32
luna/testing.py
Normal file
32
luna/testing.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
"""Provides a function to run tests in a module."""
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
def run_tests(tests_module):
|
||||
"""Runs the tests in the given module."""
|
||||
|
||||
success = False
|
||||
|
||||
tester = None
|
||||
|
||||
try:
|
||||
tester = tests_module.Tester()
|
||||
|
||||
# pylint: disable=broad-except
|
||||
except Exception:
|
||||
print('-----BEGIN LUNA ERROR-----')
|
||||
traceback.print_exc()
|
||||
print('-----END LUNA ERROR-----')
|
||||
|
||||
else:
|
||||
success = True
|
||||
print('-----LUNA SUCCESS-----')
|
||||
|
||||
finally:
|
||||
try:
|
||||
tester.stop()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
sys.exit(int(not success))
|
31
luna/tokens.py
Normal file
31
luna/tokens.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
import tiktoken
|
||||
|
||||
def num_tokens_from_messages(messages, model="gpt-3.5-turbo-0301"):
|
||||
"""Returns the number of tokens used by a list of messages."""
|
||||
try:
|
||||
encoding = tiktoken.encoding_for_model(model)
|
||||
except KeyError:
|
||||
encoding = tiktoken.get_encoding("cl100k_base")
|
||||
if model == "gpt-3.5-turbo":
|
||||
return num_tokens_from_messages(messages, model="gpt-3.5-turbo-0301")
|
||||
elif model == "gpt-4":
|
||||
return num_tokens_from_messages(messages, model="gpt-4-0314")
|
||||
elif model == "gpt-3.5-turbo-0301":
|
||||
tokens_per_message = 4 # every message follows <|start|>{role/name}\n{content}<|end|>\n
|
||||
tokens_per_name = -1 # if there's a name, the role is omitted
|
||||
elif model == "gpt-4-0314":
|
||||
tokens_per_message = 3
|
||||
tokens_per_name = 1
|
||||
else:
|
||||
raise NotImplementedError(f"""num_tokens_from_messages() is not implemented for model {model}. See https://github.com/openai/openai-python/blob/main/chatml.md for information on how messages are converted to tokens.""")
|
||||
num_tokens = 0
|
||||
for message in messages:
|
||||
num_tokens += tokens_per_message
|
||||
for key, value in message.items():
|
||||
num_tokens += len(encoding.encode(value))
|
||||
if key == "name":
|
||||
num_tokens += tokens_per_name
|
||||
num_tokens += 3 # every reply is primed with <|start|>assistant<|message|>
|
||||
return num_tokens
|
||||
|
||||
print(num_tokens_from_messages(messages))
|
12
luna/training/clock/README.md
Normal file
12
luna/training/clock/README.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# SimpleClock
|
||||
A simple web app which displays the current time.
|
||||
|
||||
## Get started
|
||||
To get started, follow these steps:
|
||||
|
||||
1. Run the app using
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
2. Open the app in your browser at http://localhost:5000
|
||||
3. Enjoy!
|
1
luna/training/clock/_prompt.luna.txt
Normal file
1
luna/training/clock/_prompt.luna.txt
Normal file
|
@ -0,0 +1 @@
|
|||
A simple website called SimpleClock. It should display the current time on /.
|
12
luna/training/clock/main.py
Normal file
12
luna/training/clock/main.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
"""Web server start module."""
|
||||
|
||||
import sys
|
||||
|
||||
from clock import create_app
|
||||
|
||||
def run(port=sys.argv[1] if len(sys.argv) > 1 else 5000):
|
||||
"""Starts the web server."""
|
||||
create_app().run(port=port)
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
13
luna/training/clock/simpleclock/__init__.py
Normal file
13
luna/training/clock/simpleclock/__init__.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
import flask
|
||||
|
||||
def create_app():
|
||||
"""Returns the Flask app."""
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""Display the current time."""
|
||||
return flask.render_template('index.html')
|
||||
|
||||
return app
|
9
luna/training/clock/simpleclock/static/index.js
Normal file
9
luna/training/clock/simpleclock/static/index.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
// use momentjs to display the time, nicely formatted.
|
||||
// updates every second
|
||||
|
||||
function update() {
|
||||
time.innerHTML = moment().format('h:mm:ss a');
|
||||
}
|
||||
|
||||
update();
|
||||
setInterval(update, 1000);
|
27
luna/training/clock/simpleclock/static/style.css
Normal file
27
luna/training/clock/simpleclock/static/style.css
Normal file
|
@ -0,0 +1,27 @@
|
|||
body {
|
||||
font-size: 1.2rem;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
#time {
|
||||
font-weight: bold;
|
||||
}
|
15
luna/training/clock/simpleclock/templates/base.html
Normal file
15
luna/training/clock/simpleclock/templates/base.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>SimpleClock</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
<script src="https://cdn.jsdelivr.net/npm/moment/moment.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='index.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
6
luna/training/clock/simpleclock/templates/index.html
Normal file
6
luna/training/clock/simpleclock/templates/index.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<h1>SimpleClock</h1>
|
||||
<p>Welcome to this website!</p>
|
||||
<p>The current time is: <span id="time"></span>.</p>
|
||||
{% endblock %}
|
26
luna/training/clock/tests.py
Normal file
26
luna/training/clock/tests.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
"""LUNA Tests."""
|
||||
|
||||
import time
|
||||
import multiprocessing
|
||||
import requests
|
||||
|
||||
class Tester:
|
||||
"""Tests if the program is working as expected."""
|
||||
|
||||
def __init__(self):
|
||||
"""Runs the tests."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import main
|
||||
|
||||
self.server = multiprocessing.Process(target=main.run, args=(5000,))
|
||||
self.server.start()
|
||||
|
||||
time.sleep(1)
|
||||
print('Server started!')
|
||||
|
||||
resp = requests.get('http://localhost:5000/', timeout=5)
|
||||
assert 'The current time is:' in resp.text
|
||||
|
||||
def stop(self):
|
||||
"""Runs after the tests are done, whether they have been successful or not."""
|
||||
self.server.terminate()
|
14
luna/training/getip/README.md
Normal file
14
luna/training/getip/README.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
# `GetIP`
|
||||
A very simple CLI app that displays the user's IP address.
|
||||
|
||||
## Get started
|
||||
It's very simple to run this app. Just run the following command in the terminal:
|
||||
|
||||
```bash
|
||||
python cli.py
|
||||
```
|
||||
|
||||
Example output:
|
||||
```
|
||||
123.123.34.34
|
||||
```
|
1
luna/training/getip/_prompt.luna.txt
Normal file
1
luna/training/getip/_prompt.luna.txt
Normal file
|
@ -0,0 +1 @@
|
|||
A very simple CLI which returns the user's IP address.
|
14
luna/training/getip/cli.py
Normal file
14
luna/training/getip/cli.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
"""Get the IP address of the client."""
|
||||
|
||||
import typer
|
||||
import requests
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
@app.command()
|
||||
def main():
|
||||
"""Shows the IP address of the client."""
|
||||
print(requests.get('https://checkip.amazonaws.com', timeout=5).text.strip())
|
||||
|
||||
if __name__ == '__main__':
|
||||
typer.run(main)
|
22
luna/training/getip/tests.py
Normal file
22
luna/training/getip/tests.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
"""LUNA Tests."""
|
||||
|
||||
import os
|
||||
import requests
|
||||
import subprocess
|
||||
|
||||
class Tester:
|
||||
"""Tests if the program is working as expected."""
|
||||
|
||||
def __init__(self):
|
||||
"""Runs the tests."""
|
||||
|
||||
# check the print output of cli.app.main()
|
||||
os.chdir(os.path.dirname(__file__))
|
||||
output = subprocess.check_output(['python', 'cli.py']).decode('utf8')
|
||||
|
||||
ip_addr = requests.get('https://checkip.amazonaws.com', timeout=5).text.strip()
|
||||
assert ip_addr in output
|
||||
|
||||
def stop(self):
|
||||
"""Runs after the tests are done, whether they have been successful or not."""
|
||||
# no need to stop anything
|
Loading…
Reference in a new issue