Use the Python Flask framework to construct the structure example of a large Web application, pythonflask

Source: Internet
Author: User

Use the Python Flask framework to construct the structure example of a large Web application, pythonflask

Although small web applications can be easily expanded with a single script, this method cannot be well expanded. As the application becomes more complex, processing in a single large source file becomes more and more problematic.

Unlike most other web frameworks, Flask does not have a specific way of organizing large projects. The structure of applications is completely determined by developers. In this chapter, we propose a possible way to organize and manage the packages and modules of a large application. This structure will be used in other examples in the book.

1. Project Structure

Example: Basic multi-file Flask application structure

|-flasky |-app/  |-templates/  |-static/  |-main/   |-__init__.py   |-errors.py   |-forms.py   |-views.py  |-__init__.py  |-email.py  |-models.py |-migrations/ |-tests/  |-__init__.py  |-test*.py |-venv/ |-requirements.txt |-config.py |-manage.py

This structure has four top-level directories:

  • The Flask application is generally placed in the directory named app.
  • The migrations directory contains the database migration script, which is the same as previously mentioned.
  • Unit Tests are stored in the test directory.
  • The venv directory contains the Python virtual environment, which is the same as previously mentioned.

There are also some new files:

  • Requirements.txt lists some dependent packages, so that you can easily deploy the same virtual environment on different computers.
  • Config. py stores some configuration settings.
  • Manage. py is used to Start applications and other application tasks.

To help you fully understand this structure, the following describes the entire process of changing the hello. py application to conform to this structure.

2. Configuration Options
The application usually requires several configuration settings. The best example is to use different databases, testing, and production environments during the development process so that they do not interfere with each other.

We can use the hierarchical structure of the configuration class to replace the simple class dictionary structure configuration in hello. py. The config. py file is shown below.

Config. py: application configuration

import osbasedir = os.path.abspath(os.path.dirname(__file__))class Config:  SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'   SQLALCHEMY_COMMIT_ON_TEARDOWN = True  FLASKY_MAIL_SUBJECT_PREFIX = '[Flasky]'  FLASKY_MAIL_SENDER = 'Flasky Admin <flasky@example.com>'   FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN')    @staticmethod  def init_app(app):     passclass DevelopmentConfig(Config):   DEBUG = True  MAIL_SERVER = 'smtp.googlemail.com'  MAIL_PORT = 587  MAIL_USE_TLS = True  MAIL_USERNAME = os.environ.get('MAIL_USERNAME')  MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')   SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \    'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')class TestingConfig(Config):   TESTING = True  SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \    'sqlite:///' + os.path.join(basedir, 'data-test.sqlite')class ProductionConfig(Config):  SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \    'sqlite:///' + os.path.join(basedir, 'data.sqlite')config = {  'development': DevelopmentConfig,  'testing': TestingConfig,  'production': ProductionConfig,  'default': DevelopmentConfig}

The Config base class contains some identical configurations. Different sub-classes define different configurations. Additional configurations can be added as needed.

To make configurations more flexible and secure, some settings can be imported from environment variables. For example, SECRET_KEY can be set in the environment due to its sensitivity, but a default value must be provided if it is not defined in the environment.

In the three configurations, the SQLALCHEMY_DATABASE_URI variable can be assigned different values. In this way, the application can run under different configurations, each of which can use different databases.

The configuration class can define a static init_app () method that uses the application instance as the parameter. Configuration-specific initialization can be executed. The Config base class implements a null init_app () method.

At the bottom of the configuration script, these different configurations are registered in the configuration dictionary. Register one of the configurations (Development Configuration) as the default configuration.

3. Application Package
The application package contains all application code, templates, and static files. It is simply called an app. You can also specify an application-specific name (if needed ). The templates and static directories are part of the application. Therefore, these two directories should be placed in the app. The database model and email support functions should also be placed in this package, each of which is saved to its own module in the form of app/models. py and app/email. py.

3.1 Use an application Factory

Creating an application in a single file is very convenient, but it has a major disadvantage. Because the application is created globally, there is no way to dynamically adapt to application configuration changes: When the script is run, the application instance has been created, so it is too late to change the configuration. This is especially important for unit tests because sometimes applications need to be run under different configurations to achieve better test coverage.

The solution to this problem is to put the application into a factory function to delay creation, so that it can be explicitly called from the script.

This not only gives the script enough time to configure, but also can be used to create multiple application instances-something useful during testing. The application factory functions defined in the constructor of the app package are displayed in Example 7-3.

This constructor imports most of the extensions currently needed, but because no application instance initializes them, it can be created but not initialized through the constructor that does not pass parameters to them. Create_app () is the application factory function. You need to input the configuration name for the application. The settings in configuration are saved in a class in config. py. You can use the from_object () method of the Flask app. config configuration object to directly import the settings. The configuration object can be selected from the config dictionary by the object name. Once an application is created and configured, the extension can be initialized. Before calling the init_app () in the extension, create and complete the initialization.

App/_ init _. py: Application Package constructor _

from flask import Flask, render_template from flask.ext.bootstrap import Bootstrap from flask.ext.mail import Mailfrom flask.ext.moment import Momentfrom flask.ext.sqlalchemy import SQLAlchemy from config import configbootstrap = Bootstrap()mail = Mail()moment = Moment()db = SQLAlchemy()def create_app(config_name):  app = Flask(__name__)   app.config.from_object(config[config_name])   config[config_name].init_app(app)    bootstrap.init_app(app)  mail.init_app(app)  moment.init_app(app)  db.init_app(app)  # attach routes and custom error pages here    return app

The factory function returns the created application instance, but note that the Applications created using the factory function in the current State are incomplete, because they do not have routing and custom error page handlers. This is the topic of the next section.

3.2. Implement application functions in the blueprint

The transformation of the application factory leads to the complexity of routing. In a single script application, the application instance is global, so it is easy to use the app. route modifier to define routes. But now the application is created at runtime, and the app. route modifier only starts to exist after create_app () is called. This is too late. As with routing, these custom error page handlers defined through the app. errorhandler decorator also have the same problem.

Fortunately, Flask uses a blueprint to provide a better solution. A blueprint is similar to an application that can define a route. The difference is that the blueprint associated with the route is in sleep state, and the route will become part of it only when the blueprint is registered in the application. Using a blueprint defined in a global scope, the routing of an application can be almost as simple as that of a single-script application.

Like an application, a blueprint can define a more structured way to create with multiple modules in a file or package. To maximize flexibility, you can create a sub-package in the application package to hold the blueprint. The following describes the constructors used to create a blueprint.

App/main/_ init _. py: Create blueprint _

from flask import Blueprintmain = Blueprint('main', __name__) from . import views, errors

A Blueprint is created by instantiating a Blueprint class object. The constructor of this class receives two parameters: the blueprint name and the position of the module or package where the blueprint is located. Like applications, in most cases, the _ name _ variable for the second parameter value is correct.

The application routes are stored in the app/main/views. py module, while the error handling program is saved in app/main/errors. py. Import these modules to associate routing and error handling with blueprints. It is important to note that the import module at the bottom of the app/init. py script should avoid circular dependency, because view. py and errors. py both need to import the main blueprint.

The blueprint is registered in the create_app () factory function like the application, as shown below.

Example app/_ init _. py: blueprint registration _

def create_app(config_name):   # ...  from .main import main as main_blueprint   app.register_blueprint(main_blueprint)  return app

The error handling is shown below.

App/main/errors. py: blueprint error handling

from flask import render_template from . import main@main.app_errorhandler(404) def page_not_found(e):  return render_template('404.html'), 404@main.app_errorhandler(500) def internal_server_error(e):  return render_template('500.html'), 500

The difference between write error handling in the blueprint is that if the errorhandler decorator is used, only the error handling caused by the blueprint will be called. App_errorhandler must be used for error handling within the application scope.

The application routing updated in the blueprint is shown here.

App/main/views. py: Application route with blueprint

from datetime import datetimefrom flask import render_template, session, redirect, url_forfrom . import mainfrom .forms import NameForm from .. import dbfrom ..models import User@main.route('/', methods=['GET', 'POST']) def index():  form = NameForm()  if form.validate_on_submit():    # ...    return redirect(url_for('.index'))   return render_template('index.html',              form=form, name=session.get('name'),              known=session.get('known', False),              current_time=datetime.utcnow())

Writing view functions in a blueprint has two major differences. First, like the previous error handling, the route decorator comes from the blueprint. The second difference is the use of the url_for () function. You may recall that the first parameter of this function is the routing node name, which specifies the default view function for the application-based routing. For example, the URL of the index () view function in a single script application can be obtained through url_for ('index.

The difference is that the Flask namespace applies to all nodes from the blueprint, so that multiple blueprints can use the same node to define view functions without conflict. The namespace is the Blueprint name (the first parameter in the Blueprint constructor), so the index () view function is registered as main. index and its URL can be obtained through url_for ('main. index.

In the blueprint, the url_for () function also supports nodes in shorter format, omitting the blueprint name, such as url_for ('. Index '). With this, you can use the blueprint of the current request. This actually means that the redirection in the same blueprint can be in a shorter form. If the redirection is performed across the blueprint, the node name with a namespace must be used.

After the application page is changed, the form object is saved in the blueprint in the app/main/forms. py module.

4. Start the script
The manage. py file in the top-level directory is used to start the application.

Manage. py: Startup Script

#!/usr/bin/env pythonimport osfrom app import create_app, dbfrom app.models import User, Rolefrom flask.ext.script import Manager, Shellfrom flask.ext.migrate import Migrate, MigrateCommandapp = create_app(os.getenv('FLASK_CONFIG') or 'default') manager = Manager(app)migrate = Migrate(app, db)def make_shell_context():  return dict(app=app, db=db, User=User, Role=Role)manager.add_command("shell", Shell(make_context=make_shell_context))manager.add_command('db', MigrateCommand)if __name__ == '__main__':   manager.run()

This script starts when you create an application. Use the environment variable FLASK_CONFIG. If it has been defined, the configuration is obtained from it. If it does not exist, the default configuration is used. The Flask-Script, Flask-Migrate, and custom context for Python shell are initialized.

For convenience, a line of execution environment will be added, so that the execution script can replace the lengthy python manage. py on the Unix-based operating system through./manage. py.

5. Requirement documents
The application packages include the requirements.txt file to record all dependency packages, including the exact version number. This is important because the virtual environment can be regenerated on different machines, such as deploying applications on machines in the production environment. This file can be automatically generated using the following pip command:

(venv) $ pip freeze >requirements.txt

Update this file after installing or updating a package. The following is an example of a requirement file:

Flask==0.10.1Flask-Bootstrap==3.0.3.1Flask-Mail==0.9.0Flask-Migrate==1.1.0Flask-Moment==0.2.0Flask-SQLAlchemy==1.0Flask-Script==0.6.6Flask-WTF==0.9.4Jinja2==2.7.1Mako==0.9.1MarkupSafe==0.18SQLAlchemy==0.8.4WTForms==1.0.5Werkzeug==0.9.4alembic==0.6.2blinker==1.3itsdangerous==0.23

To replicate a virtual environment perfectly, run the following command to create a new virtual environment:

(venv) $ pip install -r requirements.txt

When you read this file, the version number in the example requirements.txt may be outdated. Try the latest package if you like it. If you encounter any problems, you can roll back to the specified version that is compatible with the application in the requirement file at any time.

6. Unit Test
This application is so small that it does not require too many tests, but as an example, two simple test definitions are displayed in the example.

Example: tests/test_basics.py: unit test

import unittestfrom flask import current_app from app import create_app, dbclass BasicsTestCase(unittest.TestCase):   def setUp(self):    self.app = create_app('testing')    self.app_context = self.app.app_context()    self.app_context.push()    db.create_all()  def tearDown(self):     db.session.remove()     db.drop_all()     self.app_context.pop()  def test_app_exists(self):     self.assertFalse(current_app is None)  def test_app_is_testing(self):     self.assertTrue(current_app.config['TESTING'])

The compiled test uses the standard unittest package in the Python standard library. The setUp () and tearDown () Methods run before and after each test, and each method must start with test _ as a test.

Suggestion: If you want to learn more about unit test using the unittest package of Python, refer to the official documentation.
The setUp () method tries to create a test environment, similar to running an application. First, it creates application configurations for testing and activating the context. This step ensures that the test can access current_app like the regular request. Then, you can create a new database for testing as needed. The database and application context will be removed from the tearDown () method.

The first test ensures that the application instance exists. The second test ensures that the application runs under the test configuration. To ensure that the tests directory is valid, add the _ init _. py file under the tests directory. However, the file can be empty so that the unittest package can scan all modules and locate the test.

Suggestion: if you have an application cloned on GitHub, you can run git checkout 7a to switch to this version of the application. To ensure that you have installed all the dependency sets, run pip install-r requirements.txt.
To run the unit test, you can add a custom command to the manage. py script.

The following shows how to add a test command.

Example: manage. pyt: unit test Startup Script

@manager.commanddef test():  """Run the unit tests."""  import unittest  tests = unittest.TestLoader().discover('tests')   unittest.TextTestRunner(verbosity=2).run(tests)

The manager. command modifier makes it easy to implement custom commands. The decorated function name can be used as a command name, and the document string of the function displays help information. The test () function calls the test runner in the unittest package.

The unit test can be executed as follows:

(venv) $ python manage.py test
test_app_exists (test_basics.BasicsTestCase) ... oktest_app_is_testing (test_basics.BasicsTestCase) ... ok.----------------------------------------------------------------------Ran 2 tests in 0.001sOK

7. Start the database
Compared with a single-script application, the reconstructed application uses different databases.

The URL of the database obtained from the environment variable is the first choice. The default SQLite database is optional. The environment variables in the three configurations are different from those in the SQLite database file name. For example, the URL of the Development configuration is obtained from the DEV_DATABASE_URL environment variable and, if not defined, the SQLite database named data-dev.sqlite is used.

No matter which Database URL source is, you must create a database table for the new database. If Flask-Migrate is used to maintain migration tracking, the database table can be created or updated to the latest version by running the following command:

(venv) $ python manage.py db upgrade

Believe it or not, the first part has ended. You have learned the necessary basic elements of Flask, but you are not sure how to combine these scattered knowledge to form a real application. The purpose of the second part is to develop a complete application to keep you going.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.