Desarrollo de extensiones de Flask¶
Las extensiones son paquetes adicionales que añaden funcionalidad a una aplicación Flask. Aunque PyPI contiene muchas extensiones de Flask, puede que no encuentres ninguna que se ajuste a tus necesidades. Si este es el caso, puedes crear la tuya propia, y publicarla para que otros la usen también.
Esta guía mostrará cómo crear una extensión de Flask, y algunos de los patrones y requisitos comunes involucrados. Como las extensiones pueden hacer cualquier cosa, esta guía no podrá cubrir todas las posibilidades.
La mejor manera de aprender sobre las extensiones es mirar cómo están escritas otras extensiones que utilizas, y discutir con otros. Discute tus ideas de diseño con otros en nuestro Discord Chat o GitHub Discussions.
Las mejores extensiones comparten patrones comunes, de modo que quien esté familiarizado con el uso de una extensión no se sienta completamente perdido con otra. Esto sólo puede funcionar si la colaboración se produce desde el principio.
Nombramiento¶
Una extensión de Flask normalmente tiene flask
en su nombre como prefijo o sufijo. Si envuelve otra biblioteca, debería incluir el nombre de la biblioteca también. Esto facilita la búsqueda de extensiones, y hace que su propósito sea más claro.
Una recomendación general de empaquetado de Python es que el nombre de instalación del índice del paquete y el nombre utilizado en las declaraciones import
deben estar relacionados. El nombre de importación está en minúsculas, con palabras separadas por guiones bajos (_
). El nombre de instalación va en minúsculas o en mayúsculas, con las palabras separadas por guiones (-
). Si envuelve a otra biblioteca, es preferible utilizar el mismo caso que el nombre de esa biblioteca.
Estos son algunos ejemplos de nombres de instalación e importación:
Flask-Name
importado comoflask_name
flask-name-lower
importado comoflask_name_lower
Flask-ComboName
importado comoflask_comboname
Name-Flask
importado comoname_flask
La clase de extensión y la inicialización¶
Todas las extensiones necesitarán algún punto de entrada que inicialice la extensión con la aplicación. El patrón más común es crear una clase que represente la configuración y el comportamiento de la extensión, con un método init_app
para aplicar la instancia de la extensión a la instancia de la aplicación dada.
class HelloExtension:
def __init__(self, app=None):
if app is not None:
self.init_app(app)
def init_app(self, app):
app.before_request(...)
Es importante que la aplicación no se almacene en la extensión, no hagas self.app = app
. La única vez que la extensión debe tener acceso directo a una aplicación es durante init_app
, de lo contrario debe utilizar current_app`
.
Esto permite que la extensión sea compatible con el patrón de fábrica de la aplicación, evita problemas de importación circular cuando se importa la instancia de la extensión en otra parte del código de un usuario, y facilita las pruebas con diferentes configuraciones.
hello = HelloExtension()
def create_app():
app = Flask(__name__)
hello.init_app(app)
return app
Arriba, la instancia de la extensión hello
existe independientemente de la aplicación. Esto significa que otros módulos en el proyecto de un usuario pueden hacer from project import hello
y utilizar la extensión en los blueprints antes de que la aplicación exista.
El dict Flask.extensions
puede utilizarse para almacenar una referencia a la extensión en la aplicación, o algún otro estado específico de la aplicación. Ten en cuenta que se trata de un espacio de nombres único, así que utiliza un nombre único para tu extensión, como el nombre de la extensión sin el prefijo «flask».
Añadir comportamiento¶
Hay muchas maneras de que una extensión pueda añadir comportamiento. Cualquier método de configuración que esté disponible en el objeto Flask
puede utilizarse durante el método init_app
de una extensión.
Un patrón común es utilizar before_request()
para inicializar algunos datos o una conexión al principio de cada petición, y luego teardown_request()
para limpiarla al final. Esto puede ser almacenado en g
, discutido más adelante.
Un enfoque más perezoso es proporcionar un método que inicialice y almacene en caché los datos o la conexión. Por ejemplo, un método ext.get_db
podría crear una conexión a la base de datos la primera vez que se llama, para que una vista que no utilice la base de datos no cree una conexión.
Además de hacer algo antes y después de cada vista, tu extensión podría querer añadir también algunas vistas específicas. En este caso, podrías definir un Blueprint
, y luego llamar a register_blueprint()
durante init_app
para añadir el blueprint a la aplicación.
Técnicas de configuración¶
Puede haber múltiples niveles y fuentes de configuración para una extensión. Debes considerar qué partes de tu extensión entran en cada uno de ellos.
Configuración por instancia de aplicación, a través de los valores de
app.config
. Se trata de una configuración que podría cambiar razonablemente en cada despliegue de una aplicación. Un ejemplo común es una URL a un recurso externo, como una base de datos. Las claves de configuración deben comenzar con el nombre de la extensión para que no interfieran con otras extensiones.Configuración por instancia de la extensión, a través de los argumentos
__init__
. Esta configuración suele afectar al uso de la extensión, por lo que no tendría sentido cambiarla por cada despliegue.Configuración por instancia de extensión, a través de atributos de instancia y métodos de decorador. Podría ser más ergonómico asignar a
ext.value
, o utilizar un decorador@ext.register
para registrar una función, después de que la instancia de la extensión haya sido creada.Configuración global a través de atributos de clase. Cambiando un atributo de clase como
Ext.connection_class
se puede personalizar el comportamiento por defecto sin hacer una subclase. Esto podría combinarse con la configuración por extensión para anular los valores predeterminados.Subclasificación y anulación de métodos y atributos. Hacer que la API de la propia extensión sea algo que se pueda sobrescribir proporciona una herramienta muy poderosa para la personalización avanzada.
El propio objeto Flask
utiliza todas estas técnicas.
Depende de ti decidir qué configuración es la adecuada para tu extensión, en función de lo que necesites y de lo que quieras soportar.
La configuración no debe ser modificada una vez que la fase de configuración de la aplicación se ha completado y el servidor comienza a manejar las solicitudes. La configuración es global, cualquier cambio en ella no se garantiza que sea visible para otros trabajadores.
Datos durante una solicitud¶
Al escribir una aplicación Flask, el objeto g
se utiliza para almacenar información durante una petición. Por ejemplo el tutorial almacena una conexión a una base de datos SQLite como g.db
. Las extensiones también pueden usar esto, con cierto cuidado. Dado que g
es un único espacio de nombres global, las extensiones deben utilizar nombres únicos que no colisionen con los datos del usuario. Por ejemplo, utilizar el nombre de la extensión como prefijo, o como espacio de nombres.
# an internal prefix with the extension name
g._hello_user_id = 2
# or an internal prefix as a namespace
from types import SimpleNamespace
g._hello = SimpleNamespace()
g._hello.user_id = 2
The data in g
lasts for an application context. An application
context is active when a request context is, or when a CLI command is
run. If you’re storing something that should be closed, use
teardown_appcontext()
to ensure that it gets closed
when the application context ends. If it should only be valid during a
request, or would not be used in the CLI outside a request, use
teardown_request()
.
Vistas y modelos¶
Las vistas de tu extensión pueden querer interactuar con modelos específicos de tu base de datos, o con alguna otra extensión o datos conectados a tu aplicación. Por ejemplo, consideremos una extensión Flask-SimpleBlog
que trabaja con Flask-SQLAlchemy para proporcionar un modelo Post
y vistas para escribir y leer posts.
El modelo Post
necesita subclasificar el objeto db.Model
de Flask-SQLAlchemy, pero eso sólo está disponible una vez que has creado una instancia de esa extensión, no cuando tu extensión está definiendo sus vistas. Entonces, ¿cómo puede el código de la vista, definido antes de que el modelo exista, acceder al modelo?
Un método podría ser utilizar Vistas basadas en las clases. Durante __init__
, crea el modelo, luego crea las vistas pasando el modelo al método as_view()
de la clase vista.
class PostAPI(MethodView):
def __init__(self, model):
self.model = model
def get(self, id):
post = self.model.query.get(id)
return jsonify(post.to_json())
class BlogExtension:
def __init__(self, db):
class Post(db.Model):
id = db.Column(primary_key=True)
title = db.Column(db.String, nullable=False)
self.post_model = Post
def init_app(self, app):
api_view = PostAPI.as_view(model=self.post_model)
db = SQLAlchemy()
blog = BlogExtension(db)
db.init_app(app)
blog.init_app(app)
Otra técnica podría ser utilizar un atributo en la extensión, como self.post_model
de arriba. Añade la extensión a app.extensions
en init_app
, y luego accede a current_app.extensions["simple_blog"].post_model
desde views.
También puede querer proporcionar clases base para que los usuarios puedan proporcionar su propio modelo Post
que se ajuste a la API que su extensión espera. Así que podrían implementar class Post(blog.BasePost)
, y luego establecerlo como blog.post_model
.
Como puedes ver, esto puede ser un poco complejo. Desafortunadamente, no hay una solución perfecta aquí, sólo diferentes estrategias y compensaciones dependiendo de tus necesidades y de cuánta personalización quieras ofrecer. Afortunadamente, este tipo de dependencia de recursos no es una necesidad común para la mayoría de las extensiones. Recuerda, si necesitas ayuda con el diseño, pregunta en nuestro Chat de Discord o GitHub Discussions.
Directrices de extensión recomendadas¶
Anteriormente, Flask tenía el concepto de «extensiones aprobadas», donde los mantenedores de Flask evaluaban la calidad, el soporte y la compatibilidad de las extensiones antes de listarlas. Aunque la lista se volvió demasiado difícil de mantener con el tiempo, las directrices siguen siendo relevantes para todas las extensiones mantenidas y desarrolladas hoy en día, ya que ayudan a que el ecosistema de Flask siga siendo consistente y compatible.
Una extensión requiere un mantenedor. En el caso de que el autor de una extensión quiera dejar el proyecto, el proyecto debe encontrar un nuevo mantenedor y transferir el acceso al repositorio, la documentación, PyPI y cualquier otro servicio. La organización Pallets-Eco en GitHub permite el mantenimiento comunitario con la supervisión de los mantenedores de Pallets.
El esquema de nomenclatura es Flask-ExtensionName o ExtensionName-Flask. Debe proporcionar exactamente un paquete o módulo llamado
nombre_de_la_extensión
.La extensión debe utilizar una licencia de código abierto. El ecosistema web de Python tiende a preferir BSD o MIT. Debe ser de código abierto y estar disponible públicamente.
La API de la extensión debe tener las siguientes características:
Debe soportar múltiples aplicaciones que se ejecuten en el mismo proceso de Python. Usar
current_app
en lugar deself.app
, almacenar la configuración y el estado por instancia de aplicación.Debe ser posible utilizar el patrón de fábrica para crear aplicaciones. Utiliza el patrón
ext.init_app()
.
Desde un clon del repositorio, una extensión con sus dependencias debe ser instalable en modo editable con
pip install -e .
.Debe enviar pruebas que puedan ser invocadas con una herramienta común como
tox -e py
,nox -s test
opytest
. Si no se utilizatox
, las dependencias de las pruebas deben especificarse en un archivo de requisitos. Las pruebas deben formar parte de la distribución de sdist.Debe haber un enlace a la documentación o al sitio web del proyecto en los metadatos de PyPI o en el readme. La documentación debe utilizar el tema de Flask de los Temas Oficiales de Pallets.
Las dependencias de la extensión no deben usar límites superiores ni asumir ningún esquema de versión en particular, sino que deben usar límites inferiores para indicar el soporte mínimo de compatibilidad. Por ejemplo,
sqlalchemy>=1.4
.Indicate the versions of Python supported using
python_requires=">=version"
. Flask itself supports Python >=3.8 as of April 2023, but this will update over time.