Creé una aplicación de matraz que comencé a partir de un bloque if __name__ == '__main__': como vi en un tutorial. Cuando llegó el momento de iniciar la aplicación desde wsgi para su uso en producción, tuve que eliminar las opciones de port y host de app.run() y realizar cambios en la estructura del código de inicio de los que no estoy muy seguro. Ahora estoy agregando casos de prueba, lo que agrega más formas de iniciar y acceder a la aplicación ( with app.test_client() , with app.test_request_context() y quién sabe qué más). ¿Cuál es la forma correcta/recomendada de estructurar el código? que crea e inicia la aplicación, de modo que se comporte correctamente (y de manera consistente) cuando se inicia de forma independiente, desde wsgi y para pruebas?
Mi estructura actual es la siguiente:
def create_app(): """ Create and initialize our app. Does not call its run() method """ app = Flask(__name__) some_initialization(app, "config_file.json") return app app = create_app() ... # Services decorated with @app.route(...) ... if __name__ == "__main__": # The options break wsgi, I had to use `run()` app.run(host="0.0.0.0", port=5555)Para ser claros, ya he conseguido que wsgi y las pruebas funcionen, por lo que esta pregunta no se trata de cómo hacerlo; se trata de la forma recomendada de organizar los pasos de creación de estado para que el resultado se comporte como un módulo, el objeto de la aplicación se puede crear tantas veces como sea necesario, se pueden configurar los parámetros del servicio como el puerto y el servidor, etc. ¿Qué debe hacer mi código? contorno realmente parece?
Además del problema de la bandera de inicio, el código actual crea un objeto de aplicación (una vez) como efecto secundario de la importación; Podría crear más con create_app() pero from mycode import app recuperará el objeto compartido... y me pregunto acerca de todos esos decoradores que decoraron el objeto original.
Miré la documentación, pero los ejemplos están simplificados y los documentos completos presentan tantos escenarios alternativos que no puedo descifrar la estructura del código que imaginaron los creadores de Flask. Espero que esto sea simple y debe tener un patrón de código bien respaldado; ¿así que qué es lo?
Lo que hice fue:
class App: def __init__(self): # Various other initialization (eg logging, config, ...) ... self.webapp = self._start_webapp(self.app_name, self.app_port, self.log) pass def _start_webapp(self, app_name: str, app_port: Optional[int], log: logging): log.info('Running webapp...') webapp = Flask(app_name) # other flask related code ... webapp.run(debug=False, host='0.0.0.0', port=app_port) return webapp pass if __name__ == '__main__': app = App()De esta forma, puede agregar parámetros opcionales al init para anularlos durante las pruebas o anularlos a través de un cambio de configuración e incluso crear tipos adicionales de puntos finales en la misma aplicación, si lo necesita.
Descargo de responsabilidad Si bien esta no es la única estructura para Flask, se ha adaptado mejor a mis necesidades y está inspirada en los documentos oficiales de Flask sobre el uso de un patrón de fábrica.
Estructura del Proyecto Siguiendo la estructura de la Documentación
/home/user/Projects/flask-tutorial ├── flaskr/ │ ├── __init__.py │ ├── db.py │ ├── schema.sql │ ├── auth.py │ ├── blog.py │ ├── templates/ │ │ ├── base.html │ │ ├── auth/ │ │ │ ├── login.html │ │ │ └── register.html │ │ └── blog/ │ │ ├── create.html │ │ ├── index.html │ │ └── update.html │ └── static/ │ └── style.css ├── tests/ │ ├── conftest.py │ ├── data.sql │ ├── test_factory.py │ ├── test_db.py │ ├── test_auth.py │ └── test_blog.py ├── venv/ ├── setup.py └── MANIFEST.inmatrazr/, un paquete de Python que contiene el código y los archivos de su aplicación.
flaskr contendrá la fábrica para generar instancias de aplicaciones de matraz que pueden ser utilizadas por servidores WGSI y funcionarán con pruebas, orms (para migraciones), etc.
flaskr/__init__.py contiene el método de fábrica
The Factory The factory tiene como objetivo configurar y crear una aplicación Flask. Esto significa que debe pasar todas las configuraciones requeridas en una de las muchas formas aceptadas por Flask
El servidor de desarrollo de Flask espera que la función create_app() esté presente en el archivo del paquete __init__.py . Pero cuando usa un servidor de producción como los que se enumeran en los documentos , puede pasar el nombre de la función para llamar.
Una muestra de la documentación :
# flaskr/__init__.py import os from flask import Flask def create_app(test_config=None): # create and configure the app app = Flask(__name__, instance_relative_config=True) app.config.from_mapping( SECRET_KEY='dev', DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'), ) if test_config is None: # load the instance config, if it exists, when not testing app.config.from_pyfile('config.py', silent=True) else: # load the test config if passed in app.config.from_mapping(test_config) # ensure the instance folder exists try: os.makedirs(app.instance_path) except OSError: pass # a simple page that says hello @app.route('/hello') def hello(): return 'Hello, World!' return appal ejecutar un servidor de matraz de desarrollo, configure las variables de entorno como se describe
$ export FLASK_APP=flaskr $ export FLASK_ENV=development $ flask run La aplicación Routes A Flask puede tener varios módulos que requieren el objeto de la App para funcionar, como @app.route como mencionaste en los comentarios. Para manejar esto con gracia podemos hacer uso de Blueprints . Esto nos permite mantener las rutas en un archivo diferente y registrarlas en create_app()
from flask import Blueprint, render_template, abort from jinja2 import TemplateNotFound simple_page = Blueprint('simple_page', __name__, template_folder='templates') @simple_page.route('/', defaults={'page': 'index'}) @simple_page.route('/<page>') def show(page): try: return render_template(f'pages/{page}.html') except TemplateNotFound: abort(404) y podemos modificar create_app() para registrar blueprint de la siguiente manera:
def create_app(test_config=None): app = Flask(__name__, instance_relative_config=True) # configure the app . . . from yourapplication.simple_page import simple_page app.register_blueprint(simple_page) return app Deberá importar localmente el blueprint para evitar importaciones circulares. Pero esto no es elegante cuando se tienen muchos planos. Por lo tanto, podemos crear una init_blueprints(app) en el paquete blueprints como
# flaskr/blueprints/__init__.py from flaskr.blueprints.simple_page import simple_page def init_blueprints(app): with app.app_context(): app.register_blueprint(simple_page) y modifique create_app() como
from flaskr.blueprints import init_blueprints def create_app(test_config=None): app = Flask(__name__, instance_relative_config=True) # configure the app . . . init_blueprints(app) return appde esta manera su fábrica no se llena de planos. Y puede manejar el registro de planos dentro del paquete de planos según su elección. Esto también evita las importaciones circulares.
Otras extensiones Las extensiones de matraz más comunes admiten el patrón de fábrica que le permite crear un objeto de una extensión y luego llamar a obj.init_app(app) para inicializarlo con Flask App. Tomando Marshmallow aquí como ejemplo, pero se aplica a todos. Modificar create_app() como tal -
ma = Marshmallow() def create_app(test_config=None): app = Flask(__name__, instance_relative_config=True) # configure the app . . . init_blueprints(app) ma.init_app(app) return app ahora puede import ma from flaskr en cualquier archivo requerido.
Servidor de producción Como se mencionó inicialmente, los servidores WSGI de producción llamarán a create_app() para crear instancias de Flask. usando gunicorn como ejemplo, pero se pueden usar todos los servidores WSGI compatibles.
$ gunicorn "flaskr:create_app()"Puede pasar configuraciones según los documentos de gunicorn, y también se puede lograr lo mismo dentro de un script.