Flask on Google's App Engine
You can deploy your Flask app on Google's App Engine environment. In this short lesson you will learn how to get that done.
Define app.yaml file
Go to the root folder of your Flask project and
create an app.yaml file. This file contains all the configuration information of your app engine project and is the project's deployment descriptor. Here is an example app.yaml file:
runtime: python39 # or any other supported version
handlers:
- url: /static
static_dir: static
- url: /.*
script: auto
Security Best Practices: Handling Secrets
A critical rule in web development is never hardcode secret information (like API keys, database passwords, or your Flask SECRET_KEY) directly in your code.
Why is hardcoding bad?
- Security Risk: If you share your code or check it into version control (like Git), your secrets are exposed.
- Inflexible: You have to change your code to switch between development and production environments.
The Solution: Environment Variables
The standard solution is to store secrets in environment variables. Your application can then read these variables from the operating system at runtime.
For example, to set your Flask SECRET_KEY (which is required for things like Flask-WTF), you would:
- Set the variable in your terminal (before running your app):
export SECRET_KEY='some-very-secret-and-random-string' - Access it in your code (using Python's
osmodule):import os app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
This is the same principle used for the GOOGLE_APPLICATION_CREDENTIALS file path, which should also be set as an environment variable.
Example CRUD Application with Datastore
Below is an expanded example that demonstrates full CRUD (Create, Read, Delete) functionality.
The Python Code (main.py)
This code adds a root route / to list all comments and a /delete route to remove a comment.
import os
import datetime
from flask import Flask, render_template, request, redirect, url_for
from google.cloud import datastore
app = Flask(__name__)
# Set the secret key from an environment variable
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-secret-key-for-dev')
datastore_client = datastore.Client()
def get_all_comments():
"""Returns a list of all comments, ordered by date."""
query = datastore_client.query(kind='comment')
query.order = ['-date']
comments = list(query.fetch())
return comments
@app.route('/')
def root():
comments = get_all_comments()
return render_template('index.html', comments=comments)
@app.route('/submit', methods=['POST'])
def submit_form():
name = request.form['name']
email = request.form['email']
comment_text = request.form['comment_text']
entity = datastore.Entity(key=datastore_client.key('comment'))
entity.update({
'name': name,
'email': email,
'comment_text': comment_text,
'date': datetime.datetime.utcnow()
})
datastore_client.put(entity)
return redirect(url_for('root'))
@app.route('/delete/<int:comment_id>')
def delete_comment(comment_id):
key = datastore_client.key('comment', comment_id)
datastore_client.delete(key)
return redirect(url_for('root'))
if __name__ == "__main__":
app.run(debug=True)
The Templates
You will need a new index.html to list the comments and a form.
templates/index.html
<!doctype html>
<html>
<head>
<title>Comment Wall</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<h1>Leave a Comment</h1>
<form method="post" action="{{ url_for('submit_form') }}">
<label for="name">Name:</label><br>
<input type="text" name="name"><br><br>
<label for="email">Email:</label><br>
<input type="email" name="email"><br><br>
<label for="comment_text">Comment:</label><br>
<textarea name="comment_text" rows="4" cols="50"></textarea><br><br>
<input type="submit" value="Submit">
</form>
<hr>
<h2>Comments</h2>
{% for comment in comments %}
<div class="comment">
<p>
<strong>{{ comment['name'] }}</strong> ({{ comment['email'] }})
<span class="date">{{ comment['date'].strftime('%Y-%m-%d %H:%M') }}</span>
</p>
<p>{{ comment['comment_text'] }}</p>
<a href="{{ url_for('delete_comment', comment_id=comment.key.id) }}">Delete</a>
</div>
{% else %}
<p>No comments yet. Be the first!</p>
{% endfor %}
</body>
</html>
This new example provides a much more complete and realistic view of how a web application interacts with a database.