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

App Scaffold #478

Merged
merged 3 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
19 changes: 19 additions & 0 deletions scaffolds/app_engine/.gcloudignore
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
Copy link
Collaborator

Choose a reason for hiding this comment

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

not -> should not be

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done!

# 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
31 changes: 31 additions & 0 deletions scaffolds/app_engine/Makefile
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
50 changes: 50 additions & 0 deletions scaffolds/app_engine/README.md
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.
Copy link
Collaborator

Choose a reason for hiding this comment

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

remove "fully"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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`
10 changes: 10 additions & 0 deletions scaffolds/app_engine/app.yaml
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>"
Empty file.
35 changes: 35 additions & 0 deletions scaffolds/app_engine/app/server.py
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():
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe remove this internal code name and use more generic one?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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)
Dismissed Show dismissed Hide dismissed
Binary file added scaffolds/app_engine/app/static/asl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions scaffolds/app_engine/app/static/index.html
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>
161 changes: 161 additions & 0 deletions scaffolds/app_engine/app/static/main.css
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;
}
}
51 changes: 51 additions & 0 deletions scaffolds/app_engine/app/static/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
async function queryApi(prompt) {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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()
4 changes: 4 additions & 0 deletions scaffolds/app_engine/requirements.txt
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
Loading
Loading