restful - rest server python flask
Usando Alembic API desde dentro del código de la aplicación (7)
Estoy utilizando SQLite como formato de archivo de aplicación (vea here por qué querría hacer esto) para mi aplicación de escritorio basada en PySide. Es decir, cuando un usuario utiliza mi aplicación, sus datos se guardan en un solo archivo de base de datos en su máquina. Estoy utilizando el SQLAlchemy ORM para comunicarme con las bases de datos.
Cuando publico nuevas versiones de la aplicación, puedo modificar el esquema de la base de datos. No quiero que los usuarios tengan que desechar sus datos cada vez que cambio el esquema, por lo que debo migrar sus bases de datos al formato más nuevo. Además, creo mucho bases de datos temporales para guardar subconjuntos de datos para usar con algunos procesos externos. Quiero crear estas bases de datos con alambique para que estén etiquetadas con la versión adecuada.
Tengo algunas preguntas:
¿Hay alguna manera de llamar a alambique desde dentro de mi código Python? Creo que es extraño tener que usar
Popen
para un módulo de Python puro, pero los documentos solo usan alambique desde la línea de comandos. Principalmente, necesito cambiar la ubicación de la base de datos a donde esté ubicada la base de datos del usuario.Si eso no es posible, ¿puedo especificar una nueva ubicación de base de datos desde la línea de comandos sin editar el archivo .ini? Esto haría que llamar a un alambique a través de
Popen
no sea un gran problema.Veo que alembic mantiene su información de versión bajo una tabla simple llamada
alembic_version
, con una columna llamadaversion_num
y una sola fila que especifica la versión. ¿Puedo agregar una tablaalembic_version
a mi esquema y rellenarla con la última versión cuando creo nuevas bases de datos para que no haya sobrecarga? Es que incluso una buena idea; ¿Debo usar Alambique para crear todas las bases de datos?
Tengo un excelente trabajo para la única base de datos que utilizo para desarrollar en el directorio de mi proyecto. Quiero usar alembic para migrar y crear bases de datos en ubicaciones arbitrarias, preferiblemente a través de algún tipo de API de Python, y no a través de la línea de comandos. Esta aplicación también se congela con cx_Freeze, en caso de que eso haga una diferencia.
¡Gracias!
Aquí hay un ejemplo puramente programático de cómo configurar y llamar comandos alambicos mediante programación.
La configuración del directorio (para facilitar la lectura del código)
. # root dir
|- alembic/ # directory with migrations
|- tests/diy_alembic.py # example script
|- alembic.ini # ini file
Y aquí está diy_alembic.py
import os
import argparse
from alembic.config import Config
from alembic import command
import inspect
def alembic_set_stamp_head(user_parameter):
# set the paths values
this_file_directory = os.path.dirname(os.path.abspath(inspect.stack()[0][1]))
root_directory = os.path.join(this_file_directory, ''..'')
alembic_directory = os.path.join(root_directory, ''alembic'')
ini_path = os.path.join(root_directory, ''alembic.ini'')
# create Alembic config and feed it with paths
config = Config(ini_path)
config.set_main_option(''script_location'', alembic_directory)
config.cmd_opts = argparse.Namespace() # arguments stub
# If it is required to pass -x parameters to alembic
x_arg = ''user_parameter='' + user_parameter
if not hasattr(config.cmd_opts, ''x''):
if x_arg is not None:
setattr(config.cmd_opts, ''x'', [])
if isinstance(x_arg, list) or isinstance(x_arg, tuple):
for x in x_arg:
config.cmd_opts.x.append(x)
else:
config.cmd_opts.x.append(x_arg)
else:
setattr(config.cmd_opts, ''x'', None)
#prepare and run the command
revision = ''head''
sql = False
tag = None
command.stamp(config, revision, sql=sql, tag=tag)
#upgrade command
command.upgrade(config, revision, sql=sql, tag=tag)
El código es más o menos un corte de este archivo Flask-Alembic . Es un buen lugar para ver el uso y los detalles de otros comandos.
¿Por qué esta solución? - Se escribió en la necesidad de crear un sello, actualizaciones y degradaciones cuando se ejecutan pruebas automatizadas.
- os.chdir (migration_directory) interfirió con algunas pruebas.
- Queríamos tener UNA fuente de creación y manipulación de bases de datos. "Si creamos y administramos bases de datos con una base de datos de alambique, alambique, pero no metadata.create_all (), también se utilizarán para las pruebas".
- Incluso si el código anterior es más largo que 4 líneas, el alambique se mostró como una buena bestia controlable si se maneja de esta manera.
Consulte la documentación de alembic.operations.base.Operations:
from alembic.migration import MigrationContext
from alembic.operations import Operations
conn = myengine.connect()
ctx = MigrationContext.configure(conn)
op = Operations(ctx)
op.alter_column("t", "c", nullable=True)
Esta es una pregunta muy amplia, y la implementación de su idea dependerá de usted, pero es posible.
Puede llamar a Alembic desde su código de Python sin usar los comandos, ya que también está implementado en Python. Solo necesitas recrear lo que los comandos están haciendo detrás de escena.
Es cierto que los documentos no están en muy buena forma ya que son versiones relativamente tempranas de la biblioteca, pero con un poco de investigación encontrará lo siguiente:
- Crear una Config
- Utilice la configuración para crear un ScriptDirectory
- Utilice Config y ScriptDirectory para crear un EnvironmentContext
- Utilice el EnvironmentContext para crear un MigrationContext
- La mayoría de los comandos usan una combinación de métodos de Config y MigrationContext
He escrito una extensión para proporcionar este acceso programático a una base de datos Flask-SQLAlchemy. La implementación está vinculada a Flask y Flask-SQLAlchemy, pero debería ser un buen lugar para comenzar. Ver Frasco-Alambique aquí.
Con respecto a su último punto sobre cómo crear nuevas bases de datos, puede usar Alembic para crear las tablas, o puede usar metadata.create_all()
luego el alembic stamp head
(o el código de Python equivalente). Recomiendo usar siempre la ruta de migración para crear las tablas e ignorar los metadata.create_all()
procesar.
No tengo experiencia con cx_freeze, pero debería estar bien siempre y cuando las migraciones estén incluidas en la distribución y la ruta a ese directorio en el código sea correcta.
Esto es lo que he aprendido después de conectar mi software a alembic
:
¿Hay alguna manera de llamar a alambique desde dentro de mi código Python?
Sí. A partir de este momento, el punto de entrada principal para alembic.config.main
es alembic.config.main
, por lo que puede importarlo y llamarlo usted mismo, por ejemplo:
import alembic.config
alembicArgs = [
''--raiseerr'',
''upgrade'', ''head'',
]
alembic.config.main(argv=alembicArgs)
Tenga en cuenta que alembic busca migraciones en el directorio actual (es decir, os.getcwd ()). He manejado esto usando os.chdir(migration_directory)
antes de llamar a alambique, pero puede haber una mejor solución.
¿Puedo especificar una nueva ubicación de base de datos desde la línea de comandos sin editar el archivo .ini?
Sí. La clave está en el argumento de línea de comando -x
. De alembic -h
(sorprendentemente, no pude encontrar una referencia de argumento de línea de comandos en los documentos):
optional arguments:
-x X Additional arguments consumed by custom env.py
scripts, e.g. -x setting1=somesetting -x
setting2=somesetting
Así que puedes crear tu propio parámetro, por ejemplo, dbPath
, y luego interceptarlo en env.py
:
alembic -x dbPath=/path/to/sqlite.db upgrade head
entonces por ejemplo en env.py
:
def run_migrations_online():
# get the alembic section of the config file
ini_section = config.get_section(config.config_ini_section)
# if a database path was provided, override the one in alembic.ini
db_path = context.get_x_argument(as_dictionary=True).get(''dbPath'')
if db_path:
ini_section[''sqlalchemy.url''] = db_path
# establish a connectable object as normal
connectable = engine_from_config(
ini_section,
prefix=''sqlalchemy.'',
poolclass=pool.NullPool)
# etc
Por supuesto, también puede proporcionar el parámetro -x usando argv
en alembic.config.main
.
Estoy de acuerdo con @davidism sobre el uso de migraciones frente a metadata.create_all()
:)
No estoy usando Flask, por lo que no pude utilizar la biblioteca Flask-Alembic que ya estaba recomendada. En cambio, después de un poco de retoques, codifiqué la siguiente función corta para ejecutar todas las migraciones aplicables. Guardo todos mis archivos relacionados con un miembro de un miembro bajo un submódulo (carpeta) llamado migraciones. De hecho, mantengo el alembic.ini
junto con el env.py
, que es quizás un poco poco ortodoxo. Aquí hay un fragmento de mi archivo alembic.ini
para ajustarlo:
[alembic]
script_location = .
Luego agregué el siguiente archivo en el mismo directorio y lo run.py
Pero dondequiera que guarde sus scripts, todo lo que necesita hacer es modificar el código a continuación para que apunte a las rutas correctas:
from alembic.command import upgrade
from alembic.config import Config
import os
def run_sql_migrations():
# retrieves the directory that *this* file is in
migrations_dir = os.path.dirname(os.path.realpath(__file__))
# this assumes the alembic.ini is also contained in this same directory
config_file = os.path.join(migrations_dir, "alembic.ini")
config = Config(file_=config_file)
config.set_main_option("script_location", migrations_dir)
# upgrade the database to the latest revision
upgrade(config, "head")
Luego, con ese archivo run.py
en su lugar, me permite hacer esto en mi código principal:
from mymodule.migrations.run import run_sql_migrations
run_sql_migrations()
Para cualquier otra persona que intente lograr un resultado al estilo de una ruta de vuelo con SQLAlchemy, esto funcionó para mí:
Agrega migration.py a tu proyecto:
from flask_alembic import Alembic
def migrate(app):
alembic = Alembic()
alembic.init_app(app)
with app.app_context():
alembic.upgrade()
Llámelo al inicio de la aplicación después de que su db se haya inicializado
application = Flask(__name__)
db = SQLAlchemy()
db.init_app(application)
migration.migrate(application)
Entonces solo necesitas hacer el resto de los pasos de alambique estándar:
Inicializa tu proyecto como alambique.
alembic init alembic
Actualizar env.py:
from models import MyModel
target_metadata = [MyModel.Base.metadata]
Actualizar alembic.ini
sqlalchemy.url = postgresql://postgres:postgres@localhost:5432/my_db
Suponiendo que sus modelos SQLAlchemy ya están definidos, puede autogenerar sus scripts ahora:
alembic revision --autogenerate -m "descriptive migration message"
Si recibe un error por no poder importar su modelo en env.py, puede ejecutar lo siguiente en su terminal para corregirlo
export PYTHONPATH=/path/to/your/project
Por último, mis scripts de migración se estaban generando en el directorio alembic / Versiones, y tuve que copiarlos en el directorio de migraciones para que Alamembic los recogiera.
├── alembic
│ ├── env.py
│ ├── README
│ ├── script.py.mako
│ └── versions
│ ├── a5402f383da8_01_init.py # generated here...
│ └── __pycache__
├── alembic.ini
├── migrations
│ ├── a5402f383da8_01_init.py # manually copied here
│ └── script.py.mako
Probablemente tengo algo mal configurado, pero está funcionando ahora.
Si observa la página de la API de comandos desde los documentos de alambique, verá un ejemplo de cómo ejecutar los comandos de la CLI directamente desde una aplicación de Python. Sin pasar por el código CLI.
La ejecución de alembic.config.main
tiene el inconveniente de que se env.py
script env.py
que puede no ser lo que usted desea. Por ejemplo, modificará su configuración de registro.
Otra forma muy sencilla es utilizar el "API de comando" vinculado anteriormente. Por ejemplo, aquí hay una pequeña función de ayuda que terminé escribiendo:
from alembic.config import Config
from alembic import command
def run_migrations(script_location: str, dsn: str) -> None:
LOG.info(''Running DB migrations in %r on %r'', script_location, dsn)
alembic_cfg = Config()
alembic_cfg.set_main_option(''script_location'', script_location)
alembic_cfg.set_main_option(''sqlalchemy.url'', dsn)
command.upgrade(alembic_cfg, ''head'')
Estoy usando el método set_main_option
aquí para poder ejecutar las migraciones en una base de datos diferente si es necesario. Así que simplemente puedo llamar a esto de la siguiente manera:
run_migrations(''/path/to/migrations'', ''postgresql:///my_database'')
El lugar de donde obtengas esos dos valores (ruta y DSN) depende de ti. Pero esto parece estar muy cerca de lo que quieres lograr. Los comandos API también tienen los métodos stamp() que le permiten marcar un DB determinado para que sea de una versión específica. El ejemplo anterior se puede adaptar fácilmente para llamar a esto.