Manera de Pythonic para separar correctamente el modelo de la aplicación usando SQLAlchemy

Me está costando mucho hacer que mi aplicación se ejecute. La extensión Flask-SQLAlchemy crea una base de datos vacía cada vez que bash separar un módulo en paquetes. Para explicar mejor lo que estoy haciendo, permítame mostrarle cómo está estructurado mi proyecto:

Project | |-- Model | |-- __init__.py | |-- User.py | |-- Server | |-- __init__.py | |-- API | |-- __init__.py 

La idea es simple: quiero crear un paquete para mi modelo, ya que no me gusta distribuir código en un solo paquete, y “sub” proyectos separados (como API), ya que en el futuro usaré planos para mejorar aislar sub aplicaciones.

El código es muy simple:

Primero, el Model.__init__.py :

 from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() 

Tenga en cuenta que creé esto solo para usar un único objeto SQLAlchemy() el paquete. No vamos a Model.User

 from Model import db class User(db.Model): id = db.Column(db.Integer, primary_key=True) Name = db.Column(db.String(80)) Age = db.Column(db.Integer) ... 

Una vez más, note el db de importación de modelo que usé para permitir el mismo objeto db.

Finalmente, el Server.__init__.py es así:

 from flask import Flask from flask_sqlalchemy import SQLAlchemy import Model, API db = Model.db def main(): app = Flask("__main__") db = SQLAlchemy(app) db.create_all() API.SetAPIHookers(app) app.run(host="0.0.0.0", port=5000, debug=True) if __name__ == "__main__": main() 

Desde mi punto de vista, el db = SQLAlchemy(app) me permite pasar el objeto de mi aplicación sin crear una referencia circular.

El problema es que cada vez que ejecuto este código, el archivo de base de datos sqlite se crea vacío. Eso me hizo pensar que tal vez Python no importa cosas como pensé. Así que probé mi teoría eliminando el Modelo de importación y creando al usuario directamente dentro del Servidor … y ¡voilá, funcionó!

Ahora viene mi pregunta: ¿Existe una forma “pythonic” de separar correctamente los módulos como quiero o debería dejar todo en el mismo paquete?

En este momento, ha configurado su aplicación utilizando un equivalente aproximado al patrón de ” Fábrica de aplicaciones ” (llamado así por la documentación de Flask). Esta es una idea de Flask, no de Python. Tiene algunas ventajas, pero también significa que necesita hacer cosas como inicializar su objeto SQLAlchemy utilizando el método init_app lugar del constructor SQLAlchemy. No hay nada “incorrecto” en hacerlo de esta manera, pero significa que necesita ejecutar métodos como create_all() dentro de un contexto de aplicación , que actualmente no lo estaría si intentara ejecutarlo en el método main() .

Hay algunas maneras en que puede resolver esto, pero depende de usted determinar cuál desea (no hay una respuesta correcta):

No use el patrón de Application Factory

De esta manera, no creas la aplicación en una función. En su lugar, lo pones en algún lugar (como en el project/__init__.py ). Su archivo de project/__init__.py puede importar el paquete de models , mientras que el paquete de models puede importar la app del project . Esta es una referencia circular, pero está bien siempre que el objeto de la app se cree en el paquete del project antes de que el model intente importar la app del package . Consulte la documentación de Flask en Patrones de aplicación más grandes para ver un ejemplo en el que puede dividir su paquete en varios paquetes, y aún así tener estos otros paquetes para poder usar el objeto de la app mediante el uso de referencias circulares. Los doctores incluso dicen:

Todos los progtwigdores de Python los odian y, sin embargo, agregamos algunas: importaciones circulares. […] Tenga en cuenta que esta es una mala idea en general, pero aquí está realmente bien.

Si haces esto, entonces puedes cambiar tu archivo Models/__init__.py para construir el objeto SQLAlchemy con una referencia a la aplicación en el constructor. De esa manera, puede usar los create_all() y drop_all() del objeto SQLAlchemy , como se describe en la documentación de Flask-SQLAlchemy .

Guarde cómo lo tiene ahora, pero genere un request_context ()

Si continúa con lo que tiene ahora (creando su aplicación en una función), entonces necesitará comstackr el objeto SQLAlchemy en el paquete de Models sin usar el objeto de la app como parte del constructor (como lo ha hecho). En tu método principal, cambia el …

 db = SQLAlchemy(app) 

… a un …

 db.init_app(app) 

Luego, necesitaría mover el método create_all() a una función dentro del contexto de la aplicación. Una forma común de hacer esto por algo tan temprano en el proyecto sería utilizar el decorador before_first_request()

 app = Flask(...) @app.before_first_request def initialize_database(): db.create_all() 

El método “initialize_database” se ejecuta antes de que Flask maneje la primera solicitud. También puede hacer esto en cualquier momento utilizando el método app_context() :

 app = Flask(...) with app.app_context(): # This should work because we are in an app context. db.create_all() 

Tenga en cuenta que si va a seguir utilizando el patrón de Application Factory, debe comprender realmente cómo funciona el contexto de la aplicación; puede ser confuso al principio pero es necesario darse cuenta de lo que significan los errores como “aplicación no registrada en una instancia de db y ninguna aplicación vinculada al contexto actual”.

Su problema es esta línea:

 db = SQLAlchemy(app) 

debería ser esto:

 db.init_app(app) 

Al ejecutar de nuevo la aplicación SQLAlchemy, estás reasignando db al dj obj recién creado.

Intente NO alejarse de la configuración de fábrica de la aplicación. Elimina los efectos secundarios del tiempo de importación y es una buena cosa. De hecho, es posible que desee importar db dentro de su fábrica porque importar un modelo que subclasifique la Base (db.model en este caso) tiene sus propios efectos secundarios (aunque menos de un problema).

Inicializar su aplicación en un __init__.py significa que cuando importe algo de su paquete para su uso, terminará reiniciando su aplicación, incluso si no la necesita.