diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 22b6d67..0b48faf 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -4,8 +4,3 @@ ARG VARIANT=bullseye FROM mcr.microsoft.com/devcontainers/python:${VERSION}-${VARIANT} RUN python3 -m pip install --upgrade pip - -# Copy requirements.txt (if found) to a temp location so we update the environment. Also -# copy "setup.sh" so the COPY instruction does not fail if no requirements.txt exists. -COPY ../requirements.txt setup.sh /tmp/pip/ -RUN if [ -f "/tmp/pip/requirements.txt" ]; then umask 0002 && python3 -m pip install -r /tmp/pip/requirements.txt && sudo rm -rf /tmp/pip; fi diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bff8387..1ce5c7e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -19,7 +19,6 @@ // Set *default* container specific settings.json values on container create. "settings": { "[python]": { - "defaultInterpreterPath": "/opt/conda/envs/myenv/bin/python", "editor.formatOnType": true, "editor.formatOnSave": true } diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index e624149..31b2bea 100755 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -1,6 +1,8 @@ #!/bin/sh # set -eu +chmod +x ./requirements.txt && pip install -r ./requirements.txt +echo # Check if the "redis" container is running if ! docker ps --filter "status=running" --format "{{.Names}}" | grep -q "redis"; then # If the "redis" container is not running, start it using docker-compose @@ -21,10 +23,6 @@ export DATASTORE=redis export BEARER_TOKEN=footoken export PLUGIN_HOSTNAME=https://$CODESPACE_NAME-8000.$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN -echo -echo "Setting host configuration (from ./hostconfig.sh)..." -chmod +x ./hostconfig.sh && ./hostconfig.sh - echo echo "Click on GitHub Codespaces PORTS tab. Right click on port 8000, and set Port Visibility to Public. Once Port 8000 if Public, press Enter to continue..." read -r placeholder_var diff --git a/.well-known/ai-plugin.json b/.well-known/ai-plugin.json index 2eeab96..5ad3cf2 100644 --- a/.well-known/ai-plugin.json +++ b/.well-known/ai-plugin.json @@ -1,19 +1,19 @@ { - "schema_version": "v1", - "name_for_human": "TODO app", - "name_for_model": "TODO_APP", - "description_for_human": "Todo app for managing your tasks", - "description_for_model": "Todo app for managing your tasks", - "auth": { - "type": "user_http", - "authorization_type": "bearer" - }, - "api": { - "type": "openapi", - "url": "https://your-app-url.com/.well-known/openapi.yaml", - "is_user_authenticated": "false" - }, - "logo_url": "https://your-app-url.com/.well-known/logo.png", - "contact_email": "example@company.com", - "legal_info_url": "https://example.com/legal" -} + "schema_version": "v1", + "name_for_human": "Todo app", + "name_for_model": "todo_app", + "description_for_human": "Todo app for managing your tasks", + "description_for_model": "Todo app for managing your tasks", + "auth": { + "type": "user_http", + "authorization_type": "bearer" + }, + "api": { + "type": "openapi", + "url": "https://minsa110-cuddly-palm-tree-69gr759q74q24pw4-8000.preview.app.github.dev/.well-known/openapi.yaml", + "is_user_authenticated": "false" + }, + "logo_url": "https://minsa110-cuddly-palm-tree-69gr759q74q24pw4-8000.preview.app.github.dev/.well-known/logo.png", + "contact_email": "example@company.com", + "legal_info_url": "https://example.com/legal" +} \ No newline at end of file diff --git a/.well-known/openapi.yaml b/.well-known/openapi.yaml index d043e2c..fb8d485 100644 --- a/.well-known/openapi.yaml +++ b/.well-known/openapi.yaml @@ -4,19 +4,20 @@ info: description: Todo app for managing your tasks on ChatGPT version: 1.0.0 servers: - - url: https://your-app-url.com + - url: https://minsa110-laughing-eureka-979q457rx6q3p954-8000.preview.app.github.dev paths: /todos: post: summary: Create a new TODO item description: Accepts a string and adds as new TODO item operationId: create_todo - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/TodoItem" + parameters: + - in: query + name: todo + schema: + type: string + required: true + description: The description of the TODO item responses: "200": description: OK @@ -78,5 +79,11 @@ components: TodoItem: type: object properties: - title: - type: string \ No newline at end of file + todo: + type: string + todo_id: + type: integer + format: int32 + readOnly: true + required: + - todo \ No newline at end of file diff --git a/README.md b/README.md index e6a082c..3e406c7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Building an Open AI plugin with Codespaces -☁️ **To deploy your app on Azure using Azure Developer CLI and Container Apps, see the [demo-azd](https://github.com/minsa110/devcontainer-fastapi/tree/demo-azd) branch.** +☁️ **To deploy your app on Azure using Azure Developer CLI and Container Apps, see the [azd-demo](https://github.com/minsa110/devcontainer-fastapi/tree/azd-demo) branch.** This is a sample repo for developing OpenAI plugin using the FastAPI framework. There are two main parts of the repo: - Code to help setup development environment for FastAPI framework diff --git a/flush_db.py b/flushdb.py similarity index 97% rename from flush_db.py rename to flushdb.py index 9b12b58..629e03f 100644 --- a/flush_db.py +++ b/flushdb.py @@ -1,4 +1,4 @@ -import redis - -redis_client = redis.StrictRedis(host='0.0.0.0', port=6379, db=0, decode_responses=True) +import redis + +redis_client = redis.StrictRedis(host='0.0.0.0', port=6379, db=0, decode_responses=True) redis_client.flushdb() \ No newline at end of file diff --git a/hostconfig.sh b/hostconfig.sh deleted file mode 100755 index 40542cb..0000000 --- a/hostconfig.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/sh -set -eu - -# Check if CODESPACES environment variable is set to true -if [ "$CODESPACES" = "true" ]; then - # If CODESPACES is true and PLUGIN_HOSTNAME is undefined or empty, set PLUGIN_HOSTNAME - if [ -z "$PLUGIN_HOSTNAME" ]; then - # Check if CODESPACE_NAME and GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN are set - if [ -z "$CODESPACE_NAME" ] || [ -z "$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN" ]; then - echo "CODESPACE_NAME and/or GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN environment variables are not set." - exit 1 - fi - # Set PLUGIN_HOSTNAME to the expanded version of the URL - PLUGIN_HOSTNAME="https://$CODESPACE_NAME-8000.$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN" - fi -else - # If CODESPACES is not true, check if PLUGIN_HOSTNAME is set - if [ -z "$PLUGIN_HOSTNAME" ]; then - echo "PLUGIN_HOSTNAME environment variable is not set." - exit 1 - fi -fi - -# Input JSON file -json_input_file="./.well-known/ai-plugin.json" - -# Input YAML file -yaml_input_file="./.well-known/openapi.yaml" - -# Create temporary files to store the modified JSON and YAML -temp_json_file=$(mktemp) -temp_yaml_file=$(mktemp) - -# Read the JSON file and perform the substitutions using jq -jq --arg plugin_hostname "$PLUGIN_HOSTNAME" ' - .api.url = ($plugin_hostname + "/.well-known/openapi.yaml") | - .logo_url = ($plugin_hostname + "/.well-known/logo.png") -' "$json_input_file" > "$temp_json_file" - -# Find the line number where the "servers:" key is located in the YAML file -servers_line_number=$(grep -n "servers:" "$yaml_input_file" | cut -d: -f1) - -# Update the YAML file using sed and awk -awk -v line_number="$servers_line_number" -v plugin_hostname="$PLUGIN_HOSTNAME" ' - NR == line_number + 1 { - sub(/url: .*/, "url: " plugin_hostname) - } - { print } -' "$yaml_input_file" > "$temp_yaml_file" - -# Overwrite the original JSON file with the modified contents -mv "$temp_json_file" "$json_input_file" - -# Overwrite the original YAML file with the modified contents -mv "$temp_yaml_file" "$yaml_input_file" - -# Print success messages -echo "$json_input_file has been updated successfully." -echo "$yaml_input_file file has been updated successfully." diff --git a/main.py b/main.py index e5ca87f..bf63a27 100644 --- a/main.py +++ b/main.py @@ -44,4 +44,4 @@ def delete_todo(todo_id: int): if __name__ == "__main__": import uvicorn - uvicorn.run("main:app", host="0.0.0.0", port=8000, log_level="info") + uvicorn.run("main:app", host="0.0.0.0", port=8000, log_level="info") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt old mode 100644 new mode 100755 diff --git a/test_main.py b/test_main.py index b6a682d..d7be50f 100644 --- a/test_main.py +++ b/test_main.py @@ -1,46 +1,46 @@ -import pytest -from fastapi.testclient import TestClient - -from main import app - - -@pytest.fixture -def client(): - return TestClient(app) - - -def test_list_todos_empty(client): - response = client.get("/todos") - assert response.status_code == 200 - assert response.json() == {} - - -def test_list_todo_not_found(client): - response = client.get("/todos/1") - assert response.status_code == 404 - assert response.json() == {"detail": "Todo not found"} - - -def test_add_todo(client): - response = client.post("/todos", params={"todo": "Buy groceries"}) - assert response.status_code == 200 - assert response.json() == {"todo_id": 1, "todo": "Buy groceries"} - - -def test_list_todo(client): - client.post("/todos", params={"todo": "Buy groceries"}) - response = client.get("/todos/1") - assert response.status_code == 200 - assert response.json() == {"todo_id": 1, "todo": "Buy groceries"} - - -def test_delete_todo(client): - response = client.delete("/todos/1") - assert response.status_code == 200 - assert response.json() == {"result": "Todo deleted"} - - -def test_delete_todo_not_found(client): - response = client.delete("/todos/1") - assert response.status_code == 404 +import pytest +from fastapi.testclient import TestClient + +from main import app + + +@pytest.fixture +def client(): + return TestClient(app) + + +def test_list_todos_empty(client): + response = client.get("/todos") + assert response.status_code == 200 + assert response.json() == {} + + +def test_list_todo_not_found(client): + response = client.get("/todos/1") + assert response.status_code == 404 + assert response.json() == {"detail": "Todo not found"} + + +def test_add_todo(client): + response = client.post("/todos", params={"todo": "Buy groceries"}) + assert response.status_code == 200 + assert response.json() == {"todo_id": 1, "todo": "Buy groceries"} + + +def test_list_todo(client): + client.post("/todos", params={"todo": "Buy groceries"}) + response = client.get("/todos/1") + assert response.status_code == 200 + assert response.json() == {"todo_id": 1, "todo": "Buy groceries"} + + +def test_delete_todo(client): + response = client.delete("/todos/1") + assert response.status_code == 200 + assert response.json() == {"result": "Todo deleted"} + + +def test_delete_todo_not_found(client): + response = client.delete("/todos/1") + assert response.status_code == 404 assert response.json() == {"detail": "Todo not found"} \ No newline at end of file