-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
App Scaffold #478
App Scaffold #478
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# This file specifies files that are *not* uploaded to Google Cloud | ||
# using gcloud. It follows the same syntax as .gitignore, with the addition of | ||
# "#!include" directives (which insert the entries of the given .gitignore-style | ||
# file at that point). | ||
# | ||
# For more information, run: | ||
# $ gcloud topic gcloudignore | ||
# | ||
.gcloudignore | ||
# If you would like to upload your .git directory, .gitignore file or files | ||
# from your .gitignore file, remove the corresponding line | ||
# below: | ||
.git | ||
.gitignore | ||
|
||
# Python pycache: | ||
__pycache__/ | ||
# Ignored by the build system | ||
/setup.cfg |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
all: clean venv | ||
|
||
.PHONY: clean | ||
clean: | ||
@find . -name '*.pyc' -delete | ||
@find . -name '__pycache__' -delete | ||
@find . -name '*egg-info' -type d -exec rm -r {} + | ||
@find . -name '.pytest_cache' -type d -exec rm -r {} + | ||
@rm -rf venv | ||
|
||
.PHONY: venv | ||
venv: | ||
@python3 -m venv venv | ||
@. venv/bin/activate && pip install -U pip && pip install -e . | ||
|
||
.PHONY: auth | ||
auth: | ||
gcloud auth application-default login | ||
|
||
.PHONY: tests | ||
tests: | ||
./scripts/run_tests.sh | ||
|
||
.PHONY: deploy | ||
deploy: clean | ||
./scripts/create_app.sh | ||
./scripts/deploy.sh | ||
|
||
.PHONY: run | ||
run: | ||
./scripts/run_locally.sh |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# Development Workflow | ||
|
||
## Local development | ||
|
||
The workflow described here works from CloudShell or any node with the [gcloud CLI](https://cloud.google.com/sdk/docs/install) has been properly installed and authenticated. | ||
|
||
This means that you can develop your application fully locally on your laptop for example, as long as you have run `make auth` after installing the [gcloud CLI](https://cloud.google.com/sdk/docs/install) on it. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove "fully" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
|
||
The first step is to add your `PROJECT` and `BUCKET` names in the following files: | ||
* `./scripts/config.sh` | ||
* `app.yaml` | ||
|
||
For local development, install then the gcloud CLI following [these instructions](https://cloud.google.com/sdk/docs/install). | ||
|
||
Make sure to accept upgrading Python to 3.10 if prompted, then authenticate for local development by running: | ||
|
||
```bash | ||
make auth | ||
``` | ||
|
||
The second step is to create and populate the virtual environment with | ||
|
||
```bash | ||
make venv | ||
``` | ||
After this step you should find a new folder called `venv` containing the virtual environment. | ||
|
||
At this point you should already be able to run the tests by running | ||
```bash | ||
make tests | ||
``` | ||
|
||
To run the app locally, simply run | ||
```bash | ||
make run | ||
``` | ||
|
||
At last to deploy the application on AppEngine run | ||
```bash | ||
make deploy | ||
``` | ||
|
||
**Note:** `make clean` will remove all the built artifacts as long as the virtual environment created by `make venv`. This target is invoked by `make deploy` so that the built artifacts are not uploaded to AppEngine. The down-side is that the virtual environment will need to be recreated after each deployment. | ||
|
||
## Development workflow | ||
|
||
1. Edit the code | ||
1. Run the tests with `make tests` | ||
1. Test the app local with `make run` | ||
1. Deploy the app on AppEngine with `make deploy` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
runtime: python310 | ||
entrypoint: gunicorn -b :$PORT app.server:app | ||
|
||
runtime_config: | ||
operating_system: "ubuntu22" | ||
|
||
env_variables: | ||
BUCKET: "<YOUR_BUCKET>" | ||
LOCATION: "us-central1" | ||
PROJECT: "<YOUR_PROJECT>" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
""" Flask serving API and small demo UI. | ||
""" | ||
|
||
import logging | ||
|
||
from flask import Flask, jsonify, request, send_file | ||
|
||
app = Flask(__name__) | ||
|
||
|
||
@app.route("/") | ||
def _index(): | ||
"""Serve index.html in the static directory""" | ||
return send_file("static/index.html") | ||
|
||
|
||
@app.route("/myapp", methods=["GET"]) | ||
def _answernaut(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe remove this internal code name and use more generic one? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll do both! Thanks @takumiohym . |
||
return jsonify({"answer": request.args["query"]}) | ||
|
||
|
||
@app.errorhandler(500) | ||
def _server_error(e): | ||
"""Serves a formatted message on-error""" | ||
logging.exception("An error occurred during a request.") | ||
return ( | ||
f"An internal error occurred: <pre>{e}</pre><br>", | ||
500, | ||
) | ||
|
||
|
||
if __name__ == "__main__": | ||
# This is used when running locally. Gunicorn is used to run the | ||
# application on Google App Engine. See entrypoint in app.yaml. | ||
app.run(host="127.0.0.1", port=8080, debug=True) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<!doctype html> | ||
<html> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<link rel="stylesheet" href="/static/main.css"> | ||
<link rel="icon" type"image/x-icon" href="/static/asl.png"> | ||
</head> | ||
<body> | ||
<div class="body"> | ||
|
||
<div class="logo"> | ||
<img src="/static/asl.png" alt="ASL Logo" width="100" height="100"> | ||
</div> | ||
|
||
<form action="" id="query-form" class="form"> | ||
<input type="text" id="query-input" class="input"></textarea> | ||
<button id="ask-button" type="submit" class="ask">Ask</button> | ||
</form> | ||
|
||
<div id="query-answer" class="answer"></div> | ||
<div id="spinner" class="lds-spinner"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div> | ||
|
||
</div> | ||
|
||
<script type="text/javascript" src="/static/main.js"></script> | ||
|
||
|
||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
|
||
*, ::after, ::before { | ||
box-sizing: border-box; | ||
border-width: 0; | ||
border-style: solid; | ||
border-color: #e5e7eb; | ||
font-size: 1rem; | ||
font-family: ui-sans-serif, system-ui, -apple-system, "system-ui", "Segoe UI"; | ||
} | ||
|
||
.body { | ||
padding: 4rem; | ||
justify-content: center; | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
width: 100vw; | ||
height: 100vh; | ||
} | ||
|
||
.logo { | ||
margin-bottom: 2.5rem; | ||
} | ||
|
||
.form { | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
width: 50%; | ||
margin-bottom: 3rem; | ||
} | ||
|
||
.input { | ||
padding-left: 0.5rem; | ||
padding-right: 0.5rem; | ||
border-width: 1px; | ||
border-radius: 0.5rem; | ||
width: 100%; | ||
height: 2rem; | ||
} | ||
|
||
.ask { | ||
padding-top: 0.25rem; | ||
padding-bottom: 0.25rem; | ||
padding-left: 0.5rem; | ||
padding-right: 0.5rem; | ||
border-width: 1px; | ||
border-color: rgb(191 219 254); | ||
border-radius: 0.5rem; | ||
margin-left: 0.75rem; | ||
margin-top: 2rem; | ||
width: 4rem; | ||
background-color: rgb(219 234 254); | ||
} | ||
|
||
button.ask:hover { | ||
background-color: rgb(3 105 161); | ||
} | ||
|
||
.answer { | ||
display: flex; | ||
padding: 1.25rem; | ||
background-color: rgb(219 234 254); | ||
border-color: rgb(191 219 254); | ||
border-radius: 0.5rem; | ||
width: 50%; | ||
line-height: 1.4rem; | ||
display: none; | ||
overflow-y: auto; | ||
} | ||
|
||
.hide { | ||
display: none !important; | ||
} | ||
|
||
.show { | ||
display: block !important; | ||
} | ||
|
||
|
||
/* Spinner */ | ||
|
||
.lds-spinner { | ||
color: official; | ||
display: inline-block; | ||
position: relative; | ||
width: 80px; | ||
height: 80px; | ||
display: none; | ||
} | ||
.lds-spinner div { | ||
transform-origin: 40px 40px; | ||
animation: lds-spinner 1.2s linear infinite; | ||
} | ||
.lds-spinner div:after { | ||
content: " "; | ||
display: block; | ||
position: absolute; | ||
top: 3px; | ||
left: 37px; | ||
width: 6px; | ||
height: 18px; | ||
border-radius: 20%; | ||
background: rgb(66,132,243); | ||
} | ||
.lds-spinner div:nth-child(1) { | ||
transform: rotate(0deg); | ||
animation-delay: -1.1s; | ||
} | ||
.lds-spinner div:nth-child(2) { | ||
transform: rotate(30deg); | ||
animation-delay: -1s; | ||
} | ||
.lds-spinner div:nth-child(3) { | ||
transform: rotate(60deg); | ||
animation-delay: -0.9s; | ||
} | ||
.lds-spinner div:nth-child(4) { | ||
transform: rotate(90deg); | ||
animation-delay: -0.8s; | ||
} | ||
.lds-spinner div:nth-child(5) { | ||
transform: rotate(120deg); | ||
animation-delay: -0.7s; | ||
} | ||
.lds-spinner div:nth-child(6) { | ||
transform: rotate(150deg); | ||
animation-delay: -0.6s; | ||
} | ||
.lds-spinner div:nth-child(7) { | ||
transform: rotate(180deg); | ||
animation-delay: -0.5s; | ||
} | ||
.lds-spinner div:nth-child(8) { | ||
transform: rotate(210deg); | ||
animation-delay: -0.4s; | ||
} | ||
.lds-spinner div:nth-child(9) { | ||
transform: rotate(240deg); | ||
animation-delay: -0.3s; | ||
} | ||
.lds-spinner div:nth-child(10) { | ||
transform: rotate(270deg); | ||
animation-delay: -0.2s; | ||
} | ||
.lds-spinner div:nth-child(11) { | ||
transform: rotate(300deg); | ||
animation-delay: -0.1s; | ||
} | ||
.lds-spinner div:nth-child(12) { | ||
transform: rotate(330deg); | ||
animation-delay: 0s; | ||
} | ||
@keyframes lds-spinner { | ||
0% { | ||
opacity: 1; | ||
} | ||
100% { | ||
opacity: 0; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
async function queryApi(prompt) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a comment at the top of this file to describe what this file is doing. Since there's so many files it'll be good to know what the purpose of this file is. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
const endpoint = `/myapp?query=${prompt}` | ||
const response = await fetch(endpoint) | ||
const answer = await response.json() | ||
return answer | ||
} | ||
|
||
function displayAnswer(answer) { | ||
const queryAnswer = document.getElementById("query-answer") | ||
queryAnswer.innerHTML = `<md-block> ${answer} </md-block>` | ||
} | ||
|
||
function getPrompt() { | ||
const queryInput = document.getElementById("query-input") | ||
return queryInput.value | ||
} | ||
|
||
function getAnswer() { | ||
const spinner = document.getElementById("spinner") | ||
const queryAnswer = document.getElementById("query-answer") | ||
queryAnswer.classList.remove("show") | ||
spinner.classList.add("show") | ||
const prompt = getPrompt() | ||
queryApi(prompt).then( | ||
response => { | ||
spinner.classList.remove("show") | ||
queryAnswer.classList.add("show") | ||
displayAnswer(response.answer) | ||
} | ||
) | ||
|
||
} | ||
|
||
function init() { | ||
const queryForm = document.getElementById("query-form") | ||
queryForm.addEventListener("submit", e => { | ||
e.preventDefault() | ||
getAnswer() | ||
}) | ||
|
||
const queryInput = document.getElementById("query-input") | ||
queryInput.addEventListener("keyup", e => { | ||
e.preventDefault() | ||
if (e.KeyCode === 13){ | ||
document.getElementById("ask-button").click() | ||
} | ||
}) | ||
|
||
} | ||
|
||
init() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Flask==3.0.0 | ||
gunicorn==20.1.0 | ||
pre-commit==3.7.1 | ||
pytest==7.0.1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not -> should not be
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!