Managing the project structure
Flask provides developers with the convenience of building their desired project structure. It is open to any design patterns and architectural strategies for building a project directory because of its Pythonic characteristics. The focus of this discussion revolves around setting up our Online Personal Counseling System application using the simple and single-structured project approach while highlighting the different configuration variable setups.
Building the directory structure
The first aspect to consider in building the project structure is the level of complexity of the project scope. Since our project focuses only on small-scale clientele, a typical single-structured approach is enough to cater to a less scalable application. Second, we must ensure the proper layering or breakdown of various project components from the view layer down to the test modules so that the developers can identify what parts to prioritize, maintain, bug-fix, and test. The following is a screenshot of the directory structure of our prototype:
Figure 1.4 – The single-structured project directory
Chapter 2 will discuss other project structure techniques, especially when applications are scalable and complex.
Setting up a development environment
A Flask application, by default, is production-ready, even though its server, the Werkzeug’s built-in server, is not. We need to replace it with an enterprise-grade server to be fully ready for production setup. However, our goal is to set up a Flask project with a development environment that we can sample and experiment on with various features and test cases. There are three ways to set up a Flask 3.x project for development and testing purposes:
- Running the server with
app.run(debug=True)
inmain.py
. - Setting the
FLASK_DEBUG
andTESTING
built-in configuration variables totrue
in the configuration file. - Running the application with the
flask run --
debug
command.
Setting the development environment will also enable automatic reloading and the default debugger of the framework. However, turn off debugging mode after deploying the application to production to avoid security risks for the applications and software logging problems. The following screenshot shows the server log when running a Flask project with a development environment setup:
Figure 1.5 – The server log of Flask’s built-in server
Figure 1.5 shows that debug mode is set to ON
and that the debugger is enabled and given a PIN
value.
Implementing the main.py module
When creating a simple project like our specimen, the main module usually contains the Flask instantiation and some of its parameters (for example, template_folder
for the new directory of the HTML templates) and the required imports of the views below it. The following is the complete code of our main.py
file:
from flask import Flask from converter.date_converter import DateConverter app = Flask(__name__, template_folder='pages') app.url_map.converters['date'] = DateConverter @app.route('/', methods = ['GET']) def index(): return "This is an online … counseling system (OPCS)" import views.index import views.certificates import views.signup import views.examination import views.reports import views.admin import views.login import views.profile app.add_url_rule('/certificate/terminate/<string:counselor>/<date:effective_date>/<string:patient>', 'show_honor_dissmisal', views.certificates.show_honor_dissmisal) if __name__ == '__main__': app.run()
The imports to the views are placed below the Flask instantiation to avoid circular dependency problems. In this type of project structure, conflict always happens when a view module imports the app
instance of the main module while the main module has the imports to the views declared at the beginning. This occurrence is called a circular dependency between two modules importing components from each other, which leads to some circular import issues. To avoid this problem with the main and view modules, the area below the Flask instantiation is where we place these view imports. The if
statement at the bottom of main.py
, on the other hand, verifies that only the main module can run the Flask server through the app.run()
command.
The main module usually sets the configuration settings through its app
instance to build the sessions and other context-based objects or integrate other custom components, such as the security and database modules. But the ideal setup doesn’t recommend including them there; instead, you should place them separately from the code, say using a configuration file, to seamlessly manage the environment variables when configuration blunders arise, to avoid performance degradation or congestion when the Flask app
instance has several variables to load at server startup, and to replicate and back up the environment settings with less effort during project migration or replication.
Creating environment variables
Configuration variables will always be part of any project setup, and how the frameworks or platforms manage them gives an impression of the kind of framework they are. A good framework should be able to decouple both built-in and custom configuration variables from the implementation area while maintaining their easy access across the application. It can support having a configuration file that can do the following:
- Contain the variables in a structured and readable manner.
- Easily integrate with the application.
- Allow comments to be part of its content.
- Work even when deployed to other servers or containers.
- Decouple the variables from the implementation area.
Aside from the .env
file, Flask can also support configuration files in JSON, Python, and Tom’s Obvious Minimal Language (TOML) format. Flask will not require an extension module if configuration files are in JSON and Python formats. The following is the application’s config.json
file, which contains the database and Flask development environment settings:
{ «DB_USER» : «postgres», «DB_PASS» : «admin2255», «DB_PORT» : 5433, "DB_HOST" : "localhost", "DB_NAME" : "opcs", "FLASK_DEBUG" : true, "TESTING": true }
This next is a Python config.py
file with the same variable settings in config.json
:
DB_USER = «postgres» DB_PASS = «admin2255» DB_PORT = 5433 DB_HOST = "localhost" DB_NAME = "opcs" FLASK_DEBUG = True TESTING = True
The app
instance has the config
attribute with a from_file()
method that can load the JSON file, as shown in the following snippet:
app.config.from_file("config.json", load=json.load)
On the other hand, config
has a from_pyfile()
method that can manage the Python config file when invoked, as shown in this snippet:
app.config.from_pyfile('myconfig.py')
The recent addition to the supported type, TOML, requires Flask to install the toml
extension module before loading the .toml
file into the platform. After running the pip install toml
command, the config
attribute’s from_file()
method can now load the following settings of the config.toml
file:
DB_USER = «postgres» DB_PASS = «admin2255» DB_PORT = 5433 DB_HOST = "localhost" DB_NAME = "opcs" FLASK_DEBUG = true TESTING = true
TOML, like JSON and Python, has data types. It supports arrays and tables and has structural patterns that may seem more complex than the JSON and Python configuration syntax. A TOML file will have the .
toml
extension.
When accessing variables from these file types, the Flask instance uses its config
object to access each variable. This can be seen in the following version of our db.py
module for database connectivity, which uses the config.toml
file:
from __main__ import app import psycopg2 import functools def connect_db(func): @functools.wraps(func) def repo_function(*args, **kwargs): conn = psycopg2.connect( host=app.config['DB_HOST'], database=app.config['DB_NAME'], port=app.config['DB_PORT'], user=app.config['DB_USER'], password=app.config['DB_PASS']) resp = func(conn, *args, **kwargs) conn.commit() conn.close() return resp return repo_function