Skip to content

Commit

Permalink
Merge pull request #478 from GoogleCloudPlatform/app-scaffold-benoit
Browse files Browse the repository at this point in the history
App Scaffold
  • Loading branch information
BenoitDherin authored Jun 4, 2024
2 parents f66fbaf + c25ae4a commit a762bda
Show file tree
Hide file tree
Showing 18 changed files with 463 additions and 0 deletions.
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
# 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.

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():
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)
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) {
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

0 comments on commit a762bda

Please sign in to comment.