Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • mrg119/helloapp
  • cav119/mellamocarlos
  • ac3419/demonotesapp
  • cav119/mellamocarloss
  • rbc/rbctestapp
  • pb719/pranavtest
  • cav119/dasdasdasd
  • cav119/fsdfddffffffffffff
  • cav119/holasoycarlos
  • cav119/carnedehuevo
  • cav119/dsasdasdgthhhtytthht
  • ac3419/notes
  • cav119/dsasdasdgth
  • cav119/dfsdsfsdfdffddsffdsf
  • cav119/demoapp2
  • cav119/demoapp5
  • cav119/demoapp8
  • cav119/demoapp13
  • cav119/demoapp15
  • mk3918/michaelapp
  • cav119/mellamocarlosvalencia
  • cav119/mellamocarlosvalencias
  • cav119/idontknowwhy
  • mrg119/mikeapp
  • cav119/demoapp3
  • cav119/demoapp6
  • cav119/demoapp9
  • cav119/demoapp16
  • cav119/demoapp18
  • cav119/finalcountdownnino
  • cav119/cacadelavaca
  • ac3419/frontendtest1
  • cav119/helloiamcarlos
  • paas-templates/python-flask-template
  • cav119/hellocarlosv
  • cav119/hellopranav2
  • cav119/demoapp4
  • cav119/demoapp7
  • cav119/demoapp11
  • cav119/demoapp12
  • cav119/demoapp14
  • cav119/demoapp17
  • pb719/makeanapp
  • pb719/demoapp
  • rbc/examplepaasapp
45 results
Show changes
Commits on Source (33)
Showing
with 742 additions and 0 deletions
**/__pycache__/**
.idea/*
venv/*
migrations/dev/**
migrations/prod/**
\ No newline at end of file
stages:
- tests
- deploy
testing:
stage: tests
script:
- echo "TODO - Add tests"
deploy:
stage: deploy
only:
- master
script:
- chmod +x ./setup_push.sh
- ./setup_push.sh ssh://dokku@cloud-vm-42-75.doc.ic.ac.uk:22/$APP_NAME master
web: gunicorn app:app --log-file=-
\ No newline at end of file
# Python3 Flask Template
A starter template for Flask web apps using **Python 3.8**. This starter template includes:
- Dynamic frontend templates (not React)
- Database support
- Imperial LDAP user authentication
- Multiple environments (development and production)
This template is useful if you would like to create a backend REST API, optionally coupled with a simple dynamic frontend.
## Getting started
Once you have created your new app, take a few minutes to look through the files to familiarise yourself with the project structure.
- `app.py` : entry point to the Flask app
- `templates/` : contains the frontend dynamic HTML files
- `static/` : contains the static frontend assets (images, stylesheets and scripts)
- `blueprints/` : contains all the application routes
- `models/` : contains all the database models
- `database/` : contains the database creation
- `config/` : contains the app settings for the different environments
**The first change you should make** is to set the `APP_NAME` variable in `config/config.py` to whatever you app's name is.
To start the application locally, you can just run `python3 app.py` and this will launch the app on port 5000 (by default).
You will notice a message in the console saying:
`WARNING: Could not connect to the given database URL!`
To fix this, you should set the environment variable DATABASE_URL accordingly. If you have PostgreSQL running locally, you can use that. Alternatively, you could use SQLite which is much simpler and does not require installation.
If you do not want to use a database yet, you can ignore this warning and delete any routes that interact with the database.
If you navigate to `http://localhost:5000`, you will see the response created by the route defined in `blueprints/home.py`.
You will also notice the lines `Environment: production` and `Debug mode: off` when the Flask application starts in the console. To enable debug mode, you must set the environment variable `ENV` to `dev`, ie: `export ENV=dev` (see `config/config.py` for more details on different environments).
# Flask Routes
A "route manager" ("router") in Flask is called a "Blueprint", it has the role of handling requests from all routes that start with the provided "url_prefix". It can be thought of as a "subrouter", with the "superrouter" being the Flask app itself, checking for a match against all registered "subrouters".
All blueprints need to be "registered" in `app.py`, otherwise they will not be considered when path matching, and therefore will not be accessible.
# Flask Migrations and Databases
A table in flask is called a "Model", all models need to extend "db.Model". You have been provided with two models, "Person" and "PhoneNumber" which is a 1-many relationship; a "Person" can be many "PhoneNumber".
## Setup
It is very useful to have some view of your database as you develop to see whether migrations have actually been performed, and to view your data. We recommend "PgAdmin" if you are going to use PostgreSQL connection strings.
Download link: https://www.pgadmin.org/download/
### Tutorial: Setup remote connection to database with PgAdmin
1. Right-click on "Servers".
2. Create => Server...
4. Go to "Connection"
5. Enter for "Host" the IP address (or hostname) of the server which runs PostgreSQL Server.
6. Enter the "Port", default is `5432`.
7. Enter the "Username", default is `postgres`.
8. Enter the "Password", no default, the server should've been setup with one.
9. Hit "Save".
### Tutorial: Create a database remotely with PgAdmin
1. Click the server you remotely connected to in the previous tutorial.
2. Right-click on "Databases".
3. Create two new databases: one called "first_db_prod", and another called "first_db_debug".
4. Form two connection strings using the format below:
`postgresql://<username>:<password>@<host>:<port>/<db_name>`
Example: `postgresql://postgres:sAmPlE>@82.36.12.1:5432/first_db`
5. Go to the next tutorial below.
### Tutorial: connect to your database
You will need to specify the database connection strings for development ("debug" or "dev") and for production ("prod").
1. Navigate to: `config/config.py`
2. CTRL + F, then type in: db_configs
3. You'll see a single entry with two specified environment variables.
4. For each environment variable, configure your computer to set these as user environment variables. In Windows do the following: Control Panel => Search: environment => Edit the environment variables for your account => "New" => ...
5. Reload your IDE so the new environment variables values take effect.
Notes
- To speed up development you may use the "force" optional parameter variants, however this is discouraged as it exposes your connection string in your git repository.
- The two connection strings must NOT be the same!
- Do not use SQLite if you plan to make use of flask migrations.
- If you do add another database, make sure you add a static variable as well under the "DB Bindings" region, as you'll need to be able to reference this binding from the outside when creating models for the database this binding will be for.
- All binding key names must be unique; they are the identifiers Flask uses to uniquely identify each database amongst all other databases in your application.
## Adding a new table
1. Navigate to: `models`; you're models should all be made here.
2. Modify the "models" `"__init__.py"` to import your model, otherwise flask will not see it and not be able to migrate it.
3. Run a new migration when you're happy with your model to auto add the table for you.
## Running a Migration
The "migrations" directory contains the following scripts you'll need to use: `RunDowngradeMigration.sh` and `RunUpgradeMigration.sh`.
When you run an "upgrade" migration for the first time subdirectories `migrations/dev` and `migrations/prod` should be constructed.
### Managing Migrations
The `migrations` directory contains all migrations you have performed on every database your app uses. It contains the subdirectories `dev` and `prod`, then subdirectories identified by the binding keys used.
You have been provided with the binding `first_db`. Running an "upgrade" migration will result in the following path being accessible:
`migrations/dev/first_db/`
It is very important that you do not delete anything from this directory, since flask migrate will also store a migration history in correspondance to the directory's contents in your database, under the `alembic` table.
### Tutorial: running an "upgrade" migration script
Flask migrate will automatically generate a migration script based on the differences between your database represented in flask (this application) and the actual database. It may be incorrect, or may not fully complete the migration you want. To resolve this, you should check the migration script, and verify it does what you want it to do.
1. Navigate to: `models/first_db_models/phone_number.py`
2. It is very useful for rows in a database to have a `created_at` attribute so you know when the row was first inserted. Comment out the new under the TODO, and remove the TODO.
3. Navigate and edit the file: `migrations/RunUpgradeMigration.sh`
4. Change the value of the 3rd argument to either `dev` or `prod`.
5. Change the value of the 4th argument to be the binding key name of the database you want to migrate. Since "PhoneNumber" is a model that exists in the database bound to the binding key name "first_db", change the value to "first_db".
6. Save the file.
7. Navigate and edit the file: `migrations/RunMigration.sh`
8. Change the migration description, that is replace "In this migration we did..." with "Added 'created_at' column to 'phoneNumber'."
9. Save the file.
10. Run: `migrations/RunUpgradeMigration.sh`
11. Navigate to: `migrations/dev/first_db/versions`
12. You should see a new python file created in here under some hash code, open the file.
13. Verify the file is doing an `add_column` operation in the `upgrade` method.
14. Complete the migration by pressing ENTER.
15. Repeat for the other database, i.e.: if you migrated on `dev`, then you should also migrate on `prod` at some point.
### Tutorial: running an "downgrade" migration script
Example: you performed an "upgrade" migration but realize that it wasn't what you wanted, or could improve the column definition, for example you realize "phone_number" should not be a column part of "Person" but rather a separate table called "PhoneNumber" with a 1-many relation.
1. Edit `migrations/RunDowngradeMigration.sh`.
2. Change the value of the 3rd argument to either `dev` or `prod`.
3. Change the value of the 4th argument to be the binding key name of the database you want to migrate. Since `PhoneNumber` is a model that exists in the database bound to the binding key name `first_db`, change the value to `first_db`.
4. Save the file, then run it.
## Troubleshooting
### Consistency violation, cannot find version
Delete/drop the `alembic_version` table from the database, and delete the associated migration directory for this table only.
### Migration script says "No changes in schema detected"
#### (1) Ignore it
This is perfectly fine, this is just Flask Migrate checking for a migration. If you just want to downgrade from a previously run migration just ignore the output, it'll take the HEAD of the migration scripts applied, and call the "downgrade" method, then move the HEAD down to the next version.
Likewise, if you ran "RunUpgradeMigration" but cancelled before continuing with the autogenerated migration script created, you can ignore the output and continue; it'll take the HEAD of the migration scripts applied, and call the "upgrade" method, then move the HEAD to that script version.
#### (2) Check whether or not you "registered" the model
Whenever you add a new model you must also modify the upper package's `__init__.py` files to import it.
# Working with the CLI
This template comes with a CLI that is enabled when your environment is in the "dev" mode. It assumes all arguments provided on running "app.py" are CLI arguments.
If you haven't already configured a "second_db", you will need to configure one for the next tutorials.
## Tutorial: Inserting Rows
You can insert rows using raw SQL queries via PgAdmin, however you can also insert rows using the CLI.
1. Lets make a mistake, run: `app.py bad cli call`.
2. Navigate to "cli_handler.py", let's see what commands are available to us.
3. CTRL + F: CLI Argument Names
4. This region contains all arguments you are allowed to specify, for example: `app.py action=insert-iris-class binding=second_db ...`.
5. The valid "binding" values are those associated with your databases, it'll allow the CLI to determine which db to operate on.
6. Now checkout the available "action" names => CTRL + F: ACTION handlers
7. Lets populate our "IrisClass" table.
8. Using the CLI type in: `app.py action=populate-iris-class binding=second_db`. After running this command you should see the table populated in PgAdmin.
9. Lets add a new "Iris Class" to the "IrisClass" table. You can inspect its associated model in: `/models/second_db_models/iris_class.py`. Use the CLI to do this. The new iris class should be called `My Lovel Iris`, and note that to specify strings with space-separation we need to wrap the value in double quotes.
`py app.py action=insert-iris-class "My Lovely Iris" binding=second_db`
NOTICE: the CLI assumes values not containing '=' are arguments for the "action" handler.
10. Lets add a new "Iris" to the "Iris" table. You can inspect its associated model in: `/models/second_db_models/iris.py`. Use the CLI to do this.
`py app.py action=insert-iris 1.1 2.2 3.3 4.4 "My Lovely Iris" binding=second_db`
NOTE: specifying class names that are not in "IrisClass" will raise an insertion error, as its a foreign key dependency value.
\ No newline at end of file
import sys
import os
from flask_migrate import Migrate
from flask import Flask, send_from_directory, request, redirect
from flask_cors import CORS
from flask_compress import Compress
from utils import DebugUtils
from database.db import lookup_db_for_binding, init_all_dbs_with_app, init_binding_to_db_for_bindings
from config.config import DEBUG_MODE, get_app_config, get_static_url
# The environment variable whose value is the PORT this application will listen on
PORT_ENV_VAR_KEY = "PORT"
# Create and configure our Flask app
app = Flask(__name__, static_url_path=get_static_url())
app.url_map.strict_slashes = False
# Enable Gzip Compression on all responses
Compress(app)
# Enable CORS for all routes
CORS(app)
# NOTE: if there is an error in your route, e.g.: printing a variable that doesn't
# exist, then you will get a CORS related error which has nothing to do with CORS
# Source: https://stackoverflow.com/questions/25594893/how-to-enable-cors-in-flask
# Load config
flask_app_config = get_app_config()
app.config.from_object(flask_app_config)
init_binding_to_db_for_bindings(flask_app_config.SQLALCHEMY_BINDS.keys())
# Initialise all databases
init_all_dbs_with_app(app)
# The import below registers all models to their respective database
from models import *
# Register all blueprints/routes
from blueprints import home_blueprint, persons_blueprint
# from blueprints.auth import auth_blueprint
app.register_blueprint(home_blueprint)
app.register_blueprint(persons_blueprint)
# app.register_blueprint(auth_blueprint)
# ===================== MIGRATION SETUP =====================
APP_CONFIG_BINDINGS_DICT_KEY = "SQLALCHEMY_BINDS"
# The below environment variables should be set by the script that initializes the migration
# This environment variable is used by "Flask_Migrate" and should not be changed
FLASK_MIGRATE_FLASK_APP_ENV_VAR_KEY = "FLASK_APP"
# This environment variable is a hack to specify the DB we want to migrate, since we have multiple dbs
# and only require/want to migrate one at a time.
FLASK_DB_BINDING_ENV_VAR_KEY = "FLASK_DB_BINDING_TO_MIGRATE"
MIGRATION_MODE = os.environ.get(FLASK_MIGRATE_FLASK_APP_ENV_VAR_KEY, None) is not None
if MIGRATION_MODE:
binding_name = os.environ.get(FLASK_DB_BINDING_ENV_VAR_KEY, None)
if not binding_name:
print("Invalid binding name specified:", binding_name)
exit(-1)
with app.app_context():
print("Initializing migration")
# Source: https://flask-migrate.readthedocs.io/en/latest/
# After the extension is initialized, a "db" group will be added to the command-line options with several
# sub-commands
bindings_dict = app.config[APP_CONFIG_BINDINGS_DICT_KEY]
# Set the main db
app.config["SQLALCHEMY_DATABASE_URI"] = bindings_dict[binding_name]
# engine = db.get_engine(flask_app, bind_name)
migrate = Migrate()
# "db" arg below needs to be the database you want the above main db to become/migrate to
db_to_migrate = lookup_db_for_binding(binding_name)
migrate.init_app(app, db_to_migrate)
# ===================== End MIGRATION SETUP. =====================
# Get arguments to this program call
if not MIGRATION_MODE:
# The Flask Migrate CLI passes arguments from its own CLI, and we don't want to handle those.
from cli_handler import handle_cli
cli_args = sys.argv[1:]
if len(cli_args) > 0:
if DEBUG_MODE:
with app.app_context():
handle_cli(app, lookup_db_for_binding, cli_args)
exit(1)
else:
DebugUtils.print_warning("Production application should not be run with CLI arguments, "
"if you intended to run the CLI please run in development mode.")
exit(-1)
# Serve all static assets for the frontend
@app.route('/static/<path:path>')
def serve_static_files(path):
return send_from_directory('static', path)
# Force secure connection
@app.before_request
def before_request():
if not DEBUG_MODE:
scheme = request.headers.get('X-Forwarded-Proto')
if scheme and scheme == 'http' and request.url.startswith('http://'):
url = request.url.replace('http://', 'https://', 1)
code = 301
return redirect(url, code=code)
# Hook any custom Jinja templating functions
from config import CUSTOM_TEMPLATE_FUNCTIONS
app.jinja_env.globals.update(CUSTOM_TEMPLATE_FUNCTIONS)
if __name__ == '__main__':
host = '127.0.0.1'
port = int(os.environ.get(PORT_ENV_VAR_KEY, 5000))
if DEBUG_MODE:
mode_desc = "debug"
else:
mode_desc = "production"
# NOTE: it will say "Environment: production", but if DEBUG_MODE=True, then ignore that.
DebugUtils.print_warning(f"Running in {mode_desc} mode")
app.run(debug=DEBUG_MODE, host=host, port=port)
from .home import home_blueprint
from .first_db_blueprints import persons_blueprint
from .persons import persons_blueprint
from flask import Blueprint, request, redirect
from config import url_for2
from models import Person
persons_blueprint = Blueprint('persons', __name__, url_prefix='/first_db/persons')
# Example CRUD route
@persons_blueprint.route('/create-person', methods=['POST'])
def create_person():
req_form = request.form
firstname = req_form.get('firstname')
age = req_form.get('age')
surname = req_form.get("surname")
try:
Person.add(firstname, surname, int(age))
return redirect(url_for2('.display_all_persons'))
except Exception as e:
return f"Could not add entity: {e}"
@persons_blueprint.route('/display-all-persons', methods=['GET'])
def display_all_persons():
try:
rows = ""
for e in Person.get_all():
rows += str(e) + "<br>"
return rows
except Exception as e:
return f"App database error: {e}"
from flask import Blueprint, render_template
home_blueprint = Blueprint('home', __name__, url_prefix='/')
@home_blueprint.route('')
def home():
return render_template('index.html')
@home_blueprint.route('/hello/<name>')
def hello(name: str):
return "Hello, " + name
from models import *
from flask_migrate import Migrate
# ========== ACTION handlers ==========
CREATE_ALL_ACTION_NAME = "create-all"
INSERT_IRIS_CLASS_ACTION_NAME = "insert-iris-class"
INSERT_IRIS_ACTION_NAME = "insert-iris"
POPULATE_IRIS_CLASS_TABLE_ACTION_NAME = "populate-iris-class"
def create_all_action_handler(db, bind_name):
db.create_all(bind=bind_name)
def insert_iris_class(db, name):
new_iris_class = IrisClass(name=name)
db.session.add(new_iris_class)
db.session.commit()
print(f"Added new IrisClass: {new_iris_class}")
def populate_iris_class_table(db):
iris_classes = ["Iris Setosa", "Iris Versicolour", "Iris Virginica"]
for iris_class in iris_classes:
insert_iris_class(db, iris_class)
def insert_iris(db, sepal_length, sepal_width, petal_length, petal_width, class_name):
# sepal_length = db.Column(db.Float, nullable=False)
# sepal_width = db.Column(db.Float, nullable=False)
# petal_length = db.Column(db.Float, nullable=False)
# petal_width = db.Column(db.Float, nullable=False)
# class_name = db.Column(db.String, db.ForeignKey('irisClass.name', ondelete="CASCADE"), nullable=False)
new_iris = Iris(sepal_length=float(sepal_length), sepal_width=float(sepal_width), petal_length=float(petal_length),
petal_width=float(petal_width), class_name=class_name)
db.session.add(new_iris)
db.session.commit()
print(f"Added new Iris: {new_iris}")
def handle_action(action, selected_binding, flask_app, db_to_perform_action_on, db_con_str, action_args):
try:
if action == CREATE_ALL_ACTION_NAME:
create_all_action_handler(db_to_perform_action_on, selected_binding)
elif action == INSERT_IRIS_CLASS_ACTION_NAME:
insert_iris_class(db_to_perform_action_on, action_args[0])
elif action == INSERT_IRIS_ACTION_NAME:
insert_iris(db_to_perform_action_on, action_args[0], action_args[1], action_args[2], action_args[3],
action_args[4])
elif action == POPULATE_IRIS_CLASS_TABLE_ACTION_NAME:
populate_iris_class_table(db_to_perform_action_on)
else:
print("No handler for action:", action)
except Exception as e:
print(f"Exception occurred by {action} handler:", e)
print("Completed action:", action)
# ========== End ACTION handlers. ==========
# CLI Argument Names
DB_BINGING_CLI_ARG_NAME = "binding"
ACTION_CLI_ARG_NAME = "action"
NAME_VALUE_SEPARATOR = "="
CLI_CALL_FORMAT = "CLI call format: " + " ".join(map(lambda arg: f"[{arg}=<value>]",
[DB_BINGING_CLI_ARG_NAME, ACTION_CLI_ARG_NAME]))
APP_CONFIG_BINDINGS_DICT_KEY = "SQLALCHEMY_BINDS"
def handle_cli(flask_app, lookup_db_for_binding, args):
print("\n\n===== Flask App CLI =====")
print(args)
# PRE: args is a list of strings like: [db_name=John, action=migrate]
selected_binding = None
selected_action = None
action_args = list()
invalid_cli_call = False
for (i, arg) in enumerate(args):
argname_argvalue_pair = arg.split(NAME_VALUE_SEPARATOR)
pair_len = len(argname_argvalue_pair)
if pair_len == 2:
arg_name = argname_argvalue_pair[0]
arg_value = argname_argvalue_pair[1]
if arg_name == DB_BINGING_CLI_ARG_NAME:
selected_binding = arg_value
elif arg_name == ACTION_CLI_ARG_NAME:
selected_action = arg_value
else:
print("Invalid argument supplied:", arg_name)
invalid_cli_call = True
elif pair_len == 1:
# Must have specified arguments
action_args.append(arg)
else:
# Erroneous
invalid_cli_call = True
if invalid_cli_call or (len(action_args) > 0 and not selected_action):
print("Invalid CLI call, must be of the format:", CLI_CALL_FORMAT)
exit(-1)
bindings_dict = flask_app.config[APP_CONFIG_BINDINGS_DICT_KEY]
handle_action(selected_action, selected_binding, flask_app, lookup_db_for_binding(selected_binding),
bindings_dict[selected_binding], action_args)
# Define any custom Jinja2 functions here
from flask import url_for
from .config import URL_PREFIX, FIRST_DB_BIND_NAME, SECOND_DB_BIND_NAME
# Get around the routing prefix issue inside the templates
def url_for2(endpoint: str, **kwargs):
return URL_PREFIX + str(url_for(endpoint, **kwargs))
CUSTOM_TEMPLATE_FUNCTIONS = {
"url": url_for2,
}
import os
from utils.debug_utils import DebugUtils
# This should match exactly the name of the app you specified
APP_NAME = "MY_APP_NAME_HERE"
# default mode is "dev" if environment variable doesn't have an assigned value
DEFAULT_MODE = "dev"
ENV = os.environ.get('ENV', DEFAULT_MODE).lower()
DEBUG_MODE = ENV != 'prod'
URL_PREFIX = f"/{APP_NAME}" if not DEBUG_MODE else ""
class AppDBConfig:
def __init__(self, bind_key_name, debug_env_var_key, prod_env_var_key, force_prod_con_str=None,
force_debug_con_str=None):
self.bind_key_name = bind_key_name
self.debug_env_var_key = debug_env_var_key
self.prod_env_var_key = prod_env_var_key
self.force_prod_con_str = force_prod_con_str
self.force_debug_con_str = force_debug_con_str
# Factory Methods
@classmethod
def construct_with_sqllite_debug_db(cls, bind_key_name, sqllite_path, prod_env_var_key, force_prod_con_str=None):
return AppDBConfig(bind_key_name, None, prod_env_var_key,
force_prod_con_str, force_debug_con_str="sqlite:///database{0}.db".format(sqllite_path))
# End Factory Methods.
def get_debug_con_str(self):
return self._get_con_str(self.debug_env_var_key, self.force_debug_con_str)
# If you have created a database for this app, the connection string will be automatically
# accessible through the DATABASE_URL environment variable.
def get_prod_con_str(self):
return self._get_con_str(self.prod_env_var_key, self.force_prod_con_str)
# Private Methods
def _print_warning(self, msg, env_key):
DebugUtils.print_warning(msg, tag=f"{env_key} - {self.bind_key_name}", tag_wrapper_pair="[]")
def _get_con_str(self, env_key, force_value):
if force_value:
url = force_value
if url.startswith("sqlite"):
self._print_warning(f"You are using an sqlite db, you will not be able to perform "
f"migrations on this db!", env_key)
else:
# Print warning if not using a hardcoded sqlite connection string
self._print_warning(f"Hardcoded connection string! Assign it to an environment variable!",
env_key)
else:
url = os.environ.get(env_key)
if url is None:
self._print_warning("Could not connect to the given database URL!", env_key)
elif url.startswith("postgres://"):
# For PostgreSQL databases, the conn string needs to start with "postgresql"
url = url.replace("postgres://", "postgresql://", 1)
return url
# ============= DB Configs =============
# ===== DB Bindings =====
# Bindings are referenced by "models" to assign the model to the correct db
FIRST_DB_BIND_NAME = "first_db"
SECOND_DB_BIND_NAME = "second_db"
# Add more bindings here
# ...
# ===== End DB Bindings =====
PROD_BINDINGS = dict()
DEV_BINDINGS = dict()
db_configs = [
# Add more DBs here!
AppDBConfig(bind_key_name=FIRST_DB_BIND_NAME,
debug_env_var_key="FIRST_DB_DEBUG_CON_STR",
prod_env_var_key="FIRST_DB_CON_STR"),
AppDBConfig(bind_key_name=SECOND_DB_BIND_NAME,
debug_env_var_key="SECOND_DB_DEBUG_CON_STR",
prod_env_var_key="SECOND_DB_CON_STR")
]
for db_config in db_configs:
PROD_BINDINGS[db_config.bind_key_name] = db_config.get_prod_con_str()
DEV_BINDINGS[db_config.bind_key_name] = db_config.get_debug_con_str()
# ============= End DB Configs =============
# Get the static URL of the app (to get around the production path issue)
def get_static_url():
if DEBUG_MODE:
return '/static'
return f'/{APP_NAME}/static'
# Get the app configuration based on the ENV environment variable (default is prod)
def get_app_config():
if DEBUG_MODE:
return DevelopmentConfig()
return ProductionConfig()
class Config(object):
DEBUG = False
TESTING = False
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_BINDS = {}
# Flask App settings for production environment
class ProductionConfig(Config):
PREFERRED_URL_SCHEME = 'https'
DEBUG = False
APPLICATION_ROOT = f"/{APP_NAME}"
# Source: https://flask-sqlalchemy.palletsprojects.com/en/2.x/binds/
SQLALCHEMY_BINDS = PROD_BINDINGS
# Flask App settings for local development environment
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_BINDS = DEV_BINDINGS
from flask_sqlalchemy import SQLAlchemy
BINDING_TO_DB = dict()
def init_binding_to_db_for_bindings(bindings):
# Create a database for each binding
for binding in bindings:
db = SQLAlchemy()
BINDING_TO_DB[binding] = db
def lookup_db_for_binding(binding):
# PRE: db exists for given binding
return BINDING_TO_DB[binding]
def init_all_dbs_with_app(flask_app):
for db in BINDING_TO_DB.values():
db.init_app(flask_app)
#!/bin/bash -e
sh RunMigration.sh downgrade dev first_db
#!/bin/bash -e
action=$1
export ENV=$2
export FLASK_APP=../app.py
export FLASK_DB_BINDING_TO_MIGRATE=$3
migrations_dir_path="$ENV/$FLASK_DB_BINDING_TO_MIGRATE"
if [ "$action" = "upgrade" ]; then
flask db init --directory $migrations_dir_path
flask db migrate -m "In this migration we did..." --directory $migrations_dir_path
elif [ "$action" != "downgrade" ]; then
read -p "Invalid action specified: $action"
exit -1
fi
input_prompt="Press any key to confirm and $action the migration..."
read -p "$input_prompt"
read -p "$input_prompt"
flask db $action --directory $migrations_dir_path
read -p "Press any key to exit."
#!/bin/bash -e
sh RunMigration.sh upgrade dev second_db
from .first_db_models import Person, PhoneNumber
from .second_db_models import Iris, IrisClass
from .person import Person
from .phone_number import PhoneNumber
from database.db import lookup_db_for_binding
from config import FIRST_DB_BIND_NAME
import sqlalchemy
db = lookup_db_for_binding(FIRST_DB_BIND_NAME)
class Person(db.Model):
# Specify that this model is for the "firstDB" db
__bind_key__ = FIRST_DB_BIND_NAME
# Default will take the LOWER-CASE of the classname as the "tablename", therefore need to set it.
__tablename__ = 'person'
id = db.Column(db.Integer, primary_key=True)
firstname = db.Column(db.String(80), unique=False, nullable=False)
age = db.Column(db.Integer, unique=False, nullable=False)
surname = db.Column(db.String, nullable=False, server_default="")
created_at = db.Column(db.DateTime, nullable=False, server_default=sqlalchemy.sql.func.now())
# Not columns
phone_numbers = db.relationship('PhoneNumber', cascade='all,delete', backref=__tablename__)
@classmethod
def add(cls, firstname, surname, age):
db.session.add(Person(firstname=firstname, surname=surname, age=int(age)))
db.session.commit()
@classmethod
def get_all(cls):
return cls.query.all()
def __repr__(self):
# The string representation of this object
return f'Person({self.id}, {self.firstname}, {self.age}, {self.created_at}, {self.phone_numbers})'