Manejo de la configuración

Las aplicaciones necesitan algún tipo de configuración. Hay diferentes configuraciones que puedes querer cambiar dependiendo del entorno de la aplicación, como activar el modo de depuración, establecer la clave secreta, y otras cosas específicas del entorno.

La forma en que Flask está diseñado normalmente requiere que la configuración esté disponible cuando la aplicación se inicia. Puedes codificar la configuración en el código, lo que para muchas aplicaciones pequeñas no es realmente tan malo, pero hay mejores maneras.

Independientemente de cómo se cargue la configuración, hay un objeto config disponible que contiene los valores de configuración cargados: El atributo config del objeto Flask. Este es el lugar donde Flask mismo pone ciertos valores de configuración y también donde las extensiones pueden poner sus valores de configuración. Pero también es donde puedes tener tu propia configuración.

Conceptos básicos de configuración

El config es en realidad una subclase de un diccionario y puede ser modificado como cualquier diccionario:

app = Flask(__name__)
app.config['TESTING'] = True

Ciertos valores de configuración también se envían al objeto Flask para que puedas leerlos y escribirlos desde allí:

app.testing = True

Para actualizar varias claves a la vez se puede utilizar el método dict.update():

app.config.update(
    TESTING=True,
    SECRET_KEY='192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
)

Características de entorno y depuración

Los valores de configuración ENV y DEBUG son especiales porque pueden comportarse de forma inconsistente si se cambian después de que la aplicación haya empezado a configurarse. Para establecer el entorno y el modo de depuración de forma fiable, pasa opciones al comando flask o utiliza variables de entorno.

El entorno de ejecución se utiliza para indicar a Flask, las extensiones y otros programas, como Sentry, en qué contexto se está ejecutando Flask. Se controla con la variable de entorno FLASK_ENV, o la opción --env cuando se utiliza el comando flask, y por defecto es production.

Al establecer env development se habilitará el modo de depuración. flask run utilizará el depurador interactivo y el recargador por defecto en modo de depuración. Para controlar esto por separado del entorno, utilice la opción --debug/--no-debug o la variable de entorno FLASK_DEBUG.

Para cambiar Flask al entorno de desarrollo y habilitar el modo de depuración, establece --env:

$ flask --app hello --env development run

Se recomienda utilizar las opciones o variables de entorno descritas anteriormente. Aunque es posible establecer ENV y DEBUG en su configuración o código, se desaconseja encarecidamente hacerlo. No pueden ser leídos antes por el comando flask, y algunos sistemas o extensiones pueden haberse configurado ya en base a un valor anterior.

Valores de configuración incorporados

Los siguientes valores de configuración son utilizados internamente por Flask:

ENV

En qué entorno se está ejecutando la aplicación. Flask y las extensiones pueden habilitar comportamientos basados en el entorno, como habilitar el modo de depuración. El atributo env se asigna a esta clave de configuración. Esto es establecido por la variable de entorno FLASK_ENV y puede no comportarse como se espera si se establece en el código.

No activar el desarrollo cuando se despliega en producción.

Por defecto: 'producción'

Changelog

Nuevo en la versión 1.0.

DEBUG

Si el modo de depuración está activado. Cuando se utiliza flask run para iniciar el servidor de desarrollo, se mostrará un depurador interactivo para las excepciones no manejadas, y el servidor se recargará cuando el código cambie. El atributo debug se asigna a esta clave de configuración. Se activa cuando ENV es 'development' y se anula con la variable de entorno FLASK_DEBUG. Puede que no se comporte como se espera si se establece en el código.

No active el modo de depuración cuando se despliegue en producción.

Por defecto: True si ENV es 'development', o False en caso contrario.

TESTING

Activar el modo de prueba. Las excepciones se propagan en lugar de ser manejadas por los manejadores de errores de la aplicación. Las extensiones también pueden cambiar su comportamiento para facilitar las pruebas. Deberías habilitar esto en tus propias pruebas.

Por defecto: False

PROPAGATE_EXCEPTIONS

Las excepciones se vuelven a lanzar en lugar de ser manejadas por los manejadores de errores de la aplicación. Si no se establece, esto es implícitamente cierto si TESTING o DEBUG está activado.

Por defecto: None

TRAP_HTTP_EXCEPTIONS

Si no hay un manejador para una excepción de tipo HTTPException, vuelve a lanzarla para que sea manejada por el depurador interactivo en lugar de devolverla como una simple respuesta de error.

Por defecto: False

TRAP_BAD_REQUEST_ERRORS

Intentar acceder a una clave que no existe desde los dicts de petición como args y form devolverá una página de error 400 Bad Request. Habilita esto para tratar el error como una excepción no manejada en su lugar para que obtengas el depurador interactivo. Esta es una versión más específica de TRAP_HTTP_EXCEPTIONS. Si no se activa, se habilita en modo de depuración.

Por defecto: None

SECRET_KEY

Una clave secreta que se utilizará para firmar de forma segura la cookie de sesión y puede ser utilizada para cualquier otra necesidad relacionada con la seguridad por las extensiones o su aplicación. Debe ser un bytes o str largo y aleatorio. Por ejemplo, copie la salida de esto a su config:

$ python -c 'import secrets; print(secrets.token_hex())'
'192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'

No reveles la clave secreta al publicar las preguntas o comprometer el código.

Por defecto: None

El nombre de la cookie de sesión. Se puede cambiar en caso de que ya tenga una cookie con el mismo nombre.

Por defecto: 'session'

La regla de coincidencia de dominio para la que será válida la cookie de sesión. Si no se establece, la cookie será válida para todos los subdominios de SERVER_NAME. Si es False, el dominio de la cookie no se establecerá.

Por defecto: None

La ruta para la que será válida la cookie de sesión. Si no se establece, la cookie será válida bajo APPLICATION_ROOT o / si no se establece.

Por defecto: None

Los navegadores no permiten el acceso de JavaScript a las cookies marcadas como «sólo HTTP» por seguridad.

Por defecto: True

Los navegadores sólo enviarán las cookies con solicitudes a través de HTTPS si la cookie está marcada como «segura». La aplicación debe ser servida a través de HTTPS para que esto tenga sentido.

Por defecto: False

Restringe cómo se envían las cookies con las solicitudes de sitios externos. Puede establecerse como 'Lax' (recomendado) o 'Strict'. Ver Opciones de Set-Cookie.

Por defecto: None

Changelog

Nuevo en la versión 1.0.

PERMANENT_SESSION_LIFETIME

Si session.permanent es verdadero, la expiración de la cookie se establecerá este número de segundos en el futuro. Puede ser un datetime.timedelta o un int.

La implementación de cookies por defecto de Flask valida que la firma criptográfica no sea más antigua que este valor.

Por defecto: timedelta(days=31) (2678400 seconds)

SESSION_REFRESH_EACH_REQUEST

Controla si la cookie se envía con cada respuesta cuando session.permanent es verdadero. Enviar la cookie cada vez (el valor por defecto) puede evitar que la sesión expire de forma más fiable, pero utiliza más ancho de banda. Las sesiones no permanentes no se ven afectadas.

Por defecto: True

USE_X_SENDFILE

Cuando sirvas archivos, establece la cabecera X-Sendfile en lugar de servir los datos con Flask. Algunos servidores web, como Apache, reconocen esto y sirven los datos de manera más eficiente. Esto sólo tiene sentido cuando se utiliza un servidor de este tipo.

Por defecto: False

SEND_FILE_MAX_AGE_DEFAULT

Cuando se sirven archivos, establece la edad máxima del control de la caché a este número de segundos. Puede ser un datetime.timedelta o un int. Anula este valor en base a cada archivo usando get_send_file_max_age() en la aplicación o blueprint.

Si es None, send_file indica al navegador que se utilizarán peticiones condicionales en lugar de una caché temporizada, lo que suele ser preferible.

Por defecto: None

SERVER_NAME

Informa a la aplicación de a qué host y puerto está vinculada. Necesario para el soporte de coincidencia de rutas de subdominio.

Si se establece, se utilizará para el dominio de la cookie de sesión si SESSION_COOKIE_DOMAIN no se establece. Los navegadores web modernos no permiten establecer cookies para dominios sin un punto. Para utilizar un dominio localmente, añada cualquier nombre que deba dirigirse a la aplicación a su archivo hosts.

127.0.0.1 localhost.dev

Si se establece, url_for puede generar URLs externas con sólo un contexto de aplicación en lugar de un contexto de solicitud.

Por defecto: None

APPLICATION_ROOT

Informa a la aplicación de la ruta en la que está montada por la aplicación/servidor web. Esto se utiliza para generar URLs fuera del contexto de una solicitud (dentro de una solicitud, el despachador es responsable de establecer SCRIPT_NAME en su lugar; ver Despacho de aplicaciones para ejemplos de configuración de despachos)

Se utilizará para la ruta de la cookie de sesión si SESSION_COOKIE_PATH no se establece.

Por defecto: '/'

PREFERRED_URL_SCHEME

Utilice este esquema para generar URLs externas cuando no esté en un contexto de solicitud.

Por defecto: 'http'

MAX_CONTENT_LENGTH

No leer más de esta cantidad de bytes de los datos de la solicitud entrante. Si no se establece y la solicitud no especifica un CONTENT_LENGTH, no se leerá ningún dato por seguridad.

Por defecto: None

JSON_AS_ASCII

Serializa objetos a JSON codificado en ASCII. Si se desactiva, el JSON devuelto por jsonify contendrá caracteres Unicode. Esto tiene implicaciones de seguridad al renderizar el JSON en JavaScript en las plantillas, y normalmente debería permanecer activado.

Por defecto: True

JSON_SORT_KEYS

Ordena las claves de los objetos JSON alfabéticamente. Esto es útil para el almacenamiento en caché porque asegura que los datos se serializan de la misma manera sin importar cuál es la semilla hash de Python. Aunque no se recomienda, se puede deshabilitar para una posible mejora del rendimiento a costa del almacenamiento en caché.

Por defecto: True

JSONIFY_PRETTYPRINT_REGULAR

Las respuestas de jsonify serán emitidas con nuevas líneas, espacios y sangría para facilitar la lectura por parte de los humanos. Siempre se activa en modo de depuración.

Por defecto: False

JSONIFY_MIMETYPE

El mimetype de las respuestas jsonify.

Por defecto: 'application/json'

TEMPLATES_AUTO_RELOAD

Recarga las plantillas cuando se modifican. Si no se establece, se habilitará en modo de depuración.

Por defecto: None

EXPLAIN_TEMPLATE_LOADING

Registra la información de depuración que rastrea cómo se cargó un archivo de plantilla. Esto puede ser útil para averiguar por qué no se ha cargado una plantilla o parece que se ha cargado un archivo incorrecto.

Por defecto: False

Avisa si las cabeceras de las cookies son mayores que esta cantidad de bytes. Por defectos a 4093. Las cookies más grandes pueden ser ignoradas silenciosamente por los navegadores. Establezca 0 para desactivar la advertencia.

Distinto en la versión 2.2: Se ha eliminado PRESERVE_CONTEXT_ON_EXCEPTION.

Changelog

Distinto en la versión 1.0: Se han eliminado LOGGER_NAME y LOGGER_HANDLER_POLICY. Consulte Registro para obtener información sobre la configuración.

Se ha añadido ENV para reflejar la variable de entorno FLASK_ENV.

Añadido SESSION_COOKIE_SAMESITE para controlar la opción SameSite de la cookie de sesión.

Añadido MAX_COOKIE_SIZE para controlar un aviso de Werkzeug.

Nuevo en la versión 0.11: SESSION_REFRESH_EACH_REQUEST, TEMPLATES_AUTO_RELOAD, LOGGER_HANDLER_POLICY, EXPLAIN_TEMPLATE_LOADING

Nuevo en la versión 0.10: JSON_AS_ASCII, JSON_SORT_KEYS, JSONIFY_PRETTYPRINT_REGULAR

Nuevo en la versión 0.9: PREFERRED_URL_SCHEME

Nuevo en la versión 0.8: TRAP_BAD_REQUEST_ERRORS, TRAP_HTTP_EXCEPTIONS, APPLICATION_ROOT, SESSION_COOKIE_DOMAIN, SESSION_COOKIE_PATH, SESSION_COOKIE_HTTPONLY, SESSION_COOKIE_SECURE

Nuevo en la versión 0.7: PROPAGATE_EXCEPTIONS, PRESERVE_CONTEXT_ON_EXCEPTION

Nuevo en la versión 0.6: MAX_CONTENT_LENGTH

Nuevo en la versión 0.5: SERVER_NAME

Nuevo en la versión 0.4: LOGGER_NAME

Configuración desde archivos Python

La configuración se vuelve más útil si puedes almacenarla en un archivo separado, idealmente ubicado fuera del paquete de la aplicación real. Esto hace posible empaquetar y distribuir tu aplicación a través de varias herramientas de manejo de paquetes (Despliegue con Setuptools) y finalmente modificar el archivo de configuración después.

Así que un patrón común es este:

app = Flask(__name__)
app.config.from_object('yourapplication.default_settings')
app.config.from_envvar('YOURAPPLICATION_SETTINGS')

Primero se carga la configuración del módulo tuaplicacion.default_settings y luego se sustituyen los valores por el contenido del archivo al que apunta la variable de entorno TUAPLICACION_SETTINGS. Esta variable de entorno se puede establecer en el shell antes de iniciar el servidor:

$ export YOURAPPLICATION_SETTINGS=/path/to/settings.cfg
$ flask run
 * Running on http://127.0.0.1:5000/

Los propios archivos de configuración son archivos reales de Python. Sólo los valores en mayúsculas se almacenan en el objeto config más tarde. Así que asegúrese de usar letras mayúsculas para sus claves de configuración.

Aquí hay un ejemplo de un archivo de configuración:

# Example configuration
SECRET_KEY = '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'

Asegúrese de cargar la configuración muy pronto, para que las extensiones tengan la capacidad de acceder a la configuración cuando se inicie. También hay otros métodos en el objeto config para cargar desde archivos individuales. Para una referencia completa, lea la documentación del objeto Config.

Configuración a partir de archivos de datos

También es posible cargar la configuración desde un archivo en un formato de su elección utilizando from_file(). Por ejemplo, para cargar desde un archivo TOML:

import toml
app.config.from_file("config.toml", load=toml.load)

O desde un archivo JSON:

import json
app.config.from_file("config.json", load=json.load)

Configuración desde las variables de entorno

Además de apuntar a los archivos de configuración utilizando variables de entorno, puede resultar útil (o necesario) controlar sus valores de configuración directamente desde el entorno. Se puede instruir a Flask para que cargue todas las variables de entorno que comiencen con un prefijo específico en la configuración utilizando from_prefixed_env().

Las variables de entorno pueden establecerse en el shell antes de iniciar el servidor:

$ export FLASK_SECRET_KEY="5f352379324c22463451387a0aec5d2f"
$ export FLASK_MAIL_ENABLED=false
$ flask run
 * Running on http://127.0.0.1:5000/

Las variables se pueden cargar y acceder a través de la configuración con una clave igual al nombre de la variable de entorno sin el prefijo, por ejemplo:

app.config.from_prefixed_env()
app.config["SECRET_KEY"]  # Is "5f352379324c22463451387a0aec5d2f"

El prefijo es FLASK_ por defecto. Esto es configurable mediante el argumento prefix de from_prefixed_env().

Los valores serán analizados para intentar convertirlos a un tipo más específico que las cadenas. Por defecto se utiliza json.loads(), por lo que cualquier valor JSON válido es posible, incluyendo listas y dicts. Esto es configurable mediante el argumento loads de from_prefixed_env().

Cuando se añade un valor booleano con el análisis JSON por defecto, sólo «true» y «false», en minúsculas, son valores válidos. Ten en cuenta que cualquier cadena no vacía es considerada True por Python.

Es posible establecer claves en diccionarios anidados separando las claves con doble guión bajo (__). Las claves intermedias que no existan en el dictado padre se inicializarán con un dict vacío.

$ export FLASK_MYAPI__credentials__username=user123
app.config["MYAPI"]["credentials"]["username"]  # Is "user123"

En Windows, las claves de las variables de entorno son siempre mayúsculas, por lo que el ejemplo anterior terminaría como MYAPI__CREDENTIALS__USERNAME.

Para obtener aún más características de carga de configuraciones, incluyendo la fusión y el soporte de Windows insensible a las mayúsculas y minúsculas, pruebe una biblioteca dedicada como Dynaconf, que incluye la integración con Flask.

Mejores prácticas de configuración

El inconveniente del enfoque mencionado anteriormente es que hace que las pruebas sean un poco más difíciles. No hay una solución única al 100% para este problema en general, pero hay un par de cosas que puedes tener en cuenta para mejorar esa experiencia:

  1. Cree su aplicación en una función y registre los blueprints en ella. De esta manera puedes crear múltiples instancias de tu aplicación con diferentes configuraciones adjuntas lo que hace que las pruebas unitarias sean mucho más fáciles. Puedes usar esto para pasar la configuración según sea necesario.

  2. No escriba código que necesite la configuración en el momento de la importación. Si te limitas a los accesos de sólo petición a la configuración, puedes reconfigurar el objeto más tarde según sea necesario.

  3. Asegúrate de cargar la configuración muy pronto, para que las extensiones puedan acceder a la configuración cuando llamen a init_app.

Desarrollo / Producción

La mayoría de las aplicaciones necesitan más de una configuración. Debería haber al menos configuraciones separadas para el servidor de producción y el utilizado durante el desarrollo. La forma más fácil de manejar esto es utilizar una configuración por defecto que siempre se carga y forma parte del control de versiones, y una configuración separada que anula los valores según sea necesario como se menciona en el ejemplo anterior:

app = Flask(__name__)
app.config.from_object('yourapplication.default_settings')
app.config.from_envvar('YOURAPPLICATION_SETTINGS')

Entonces sólo tienes que añadir un archivo config.py separado y exportar TUAPLICACION_SETTINGS=/ruta/al/config.py y ya está. Sin embargo, también hay formas alternativas. Por ejemplo, puedes usar importaciones o subclases.

Lo que es muy popular en el mundo de Django es hacer la importación explícita en el archivo de configuración añadiendo from yourapplication.default_settings import * al principio del archivo y luego anulando los cambios a mano. También puedes inspeccionar una variable de entorno como YOURAPPLICATION_MODE y establecerla como producción, desarrollo, etc. e importar diferentes archivos codificados en base a eso.

Un patrón interesante es también utilizar clases y herencia para la configuración:

class Config(object):
    TESTING = False

class ProductionConfig(Config):
    DATABASE_URI = 'mysql://user@localhost/foo'

class DevelopmentConfig(Config):
    DATABASE_URI = "sqlite:////tmp/foo.db"

class TestingConfig(Config):
    DATABASE_URI = 'sqlite:///:memory:'
    TESTING = True

Para habilitar una configuración de este tipo sólo tienes que llamar a from_object():

app.config.from_object('configmodule.ProductionConfig')

Tenga en cuenta que from_object() no instanciará el objeto de la clase. Si necesita instanciar la clase, por ejemplo para acceder a una propiedad, debe hacerlo antes de llamar a from_object():

from configmodule import ProductionConfig
app.config.from_object(ProductionConfig())

# Alternatively, import via string:
from werkzeug.utils import import_string
cfg = import_string('configmodule.ProductionConfig')()
app.config.from_object(cfg)

La instanciación del objeto de configuración le permite utilizar @property en sus clases de configuración:

class Config(object):
    """Base config, uses staging database server."""
    TESTING = False
    DB_SERVER = '192.168.1.56'

    @property
    def DATABASE_URI(self):  # Note: all caps
        return f"mysql://user@{self.DB_SERVER}/foo"

class ProductionConfig(Config):
    """Uses production database server."""
    DB_SERVER = '192.168.19.32'

class DevelopmentConfig(Config):
    DB_SERVER = 'localhost'

class TestingConfig(Config):
    DB_SERVER = 'localhost'
    DATABASE_URI = 'sqlite:///:memory:'

Hay muchas maneras diferentes y depende de ti cómo quieres gestionar tus archivos de configuración. Sin embargo aquí una lista de buenas recomendaciones:

  • Mantenga una configuración por defecto en el control de versiones. Rellene la configuración con esta configuración por defecto o impórtela en sus propios archivos de configuración antes de anular los valores.

  • Utilice una variable de entorno para cambiar entre las configuraciones. Esto se puede hacer desde fuera del intérprete de Python y hace que el desarrollo y el despliegue sean mucho más fáciles porque puedes cambiar rápida y fácilmente entre diferentes configuraciones sin tener que tocar el código en absoluto. Si trabajas a menudo en diferentes proyectos puedes incluso crear tu propio script para el sourcing que active un virtualenv y exporte la configuración de desarrollo por ti.

  • Utiliza una herramienta como fabric en producción para enviar el código y las configuraciones por separado a los servidores de producción. Para algunos detalles sobre cómo hacerlo, dirígete al patrón Despliegue con Fabric.

Carpetas de instancias

Changelog

Nuevo en la versión 0.8.

Flask 0.8 introduce las carpetas de instancia. Durante mucho tiempo Flask hizo posible referirse a rutas relativas a la carpeta de la aplicación directamente (a través de Flask.root_path). Así era también como muchos desarrolladores cargaban las configuraciones almacenadas junto a la aplicación. Sin embargo, lamentablemente esto sólo funciona bien si las aplicaciones no son paquetes, en cuyo caso la ruta raíz se refiere al contenido del paquete.

Con Flask 0.8 se introdujo un nuevo atributo: Flask.instance_path. Hace referencia a un nuevo concepto llamado “carpeta de instancia”. La carpeta de instancia está diseñada para no estar bajo control de versiones y ser específica para el despliegue. Es el lugar perfecto para dejar cosas que cambian en tiempo de ejecución o archivos de configuración.

Puedes proporcionar explícitamente la ruta de la carpeta de instancia cuando creas la aplicación Flask o puedes dejar que Flask autodetecte la carpeta de instancia. Para la configuración explícita utilice el parámetro instance_path:

app = Flask(__name__, instance_path='/path/to/instance/folder')

Por favor, tenga en cuenta que esta ruta debe ser absoluta cuando se proporciona.

Si no se proporciona el parámetro instance_path se utilizan las siguientes ubicaciones por defecto:

  • Módulo desinstalado:

    /myapp.py
    /instance
    
  • Paquete desinstalado:

    /myapp
        /__init__.py
    /instance
    
  • Módulo o paquete instalado:

    $PREFIX/lib/pythonX.Y/site-packages/myapp
    $PREFIX/var/myapp-instance
    

    $PREFIX es el prefijo de tu instalación de Python. Puede ser /usr o la ruta de tu virtualenv. Puedes imprimir el valor de sys.prefix para ver cuál es el prefijo establecido.

Dado que el objeto config proporcionaba la carga de archivos de configuración a partir de nombres de archivo relativos, hemos hecho posible cambiar la carga a través de los nombres de archivo para que sea relativa a la ruta de la instancia si se desea. El comportamiento de las rutas relativas en los archivos de configuración puede cambiarse entre «relativo a la raíz de la aplicación» (por defecto) y «relativo a la carpeta de la instancia» a través del interruptor instance_relative_config del constructor de la aplicación:

app = Flask(__name__, instance_relative_config=True)

Aquí hay un ejemplo completo de cómo configurar Flask para precargar la configuración de un módulo y luego anular la configuración de un archivo en la carpeta de instancia si existe:

app = Flask(__name__, instance_relative_config=True)
app.config.from_object('yourapplication.default_settings')
app.config.from_pyfile('application.cfg', silent=True)

La ruta de acceso a la carpeta de instancias se puede encontrar a través de Flask.instance_path. Flask también proporciona un acceso directo para abrir un archivo de la carpeta de instancia con Flask.open_instance_resource().

Ejemplo de uso de ambos:

filename = os.path.join(app.instance_path, 'application.cfg')
with open(filename) as f:
    config = f.read()

# or via open_instance_resource:
with app.open_instance_resource('application.cfg') as f:
    config = f.read()