/ / Uso de Alembic API desde el interior del código de la aplicación: python, sqlite, sqlalchemy, alembic

Uso de Alembic API desde el código de la aplicación: python, sqlite, sqlalchemy, alembic

Estoy usando SQLite como un formato de archivo de aplicación (ver aquí por qué querrías hacer esto) para miAplicació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.

A medida que lanzo nuevas versiones de la aplicación,Puede 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 a un módulo 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 la base de datos desde la línea de comandos sin editar el archivo .ini? Popen no es un gran trato.

  • Veo que alembic mantiene su información de versión bajo una tabla simple llamada alembic_version, con una columna llamada version_num y una sola fila especificando la versión. ¿Puedo agregar un alembic_version la tabla a mi esquema y la rellené 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 alambique funcionando muy bien para el single.La base de datos que utilizo para desarrollar con el directorio de mi proyecto. Quiero usar un alambique para migrar y crear convenientemente bases de datos en ubicaciones arbitrarias, preferiblemente a través de algún tipo de API de Python, y no la línea de comandos. Esta aplicación también está congelada con cx_Freeze, En caso de que eso haga una diferencia.

¡Gracias!

Respuestas

12 para la respuesta № 1

Esto es lo que he aprendido después de conectar mi software a alembic:

Sí. A partir de este escrito, el punto de entrada principal para alambique es alembic.config.main, para que puedas importarlo y llamarlo tú 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 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 -x argumento de línea de comando. Desde 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(
iniSection,
prefix="sqlalchemy.",
poolclass=pool.NullPool)

# etc

Por supuesto, puede suministrar el parámetro -x usando argv en alembic.config.main, también.

estoy de acuerdo con @davidism sobre el uso de migraciones vs metadata.create_all() :)


7 para la respuesta № 2

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 Python sin usar los comandos, ya que también está implementado en Python. Solo necesita 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 todavía son versiones relativamente tempranas de la biblioteca, pero con un poco de investigación encontrará lo siguiente:

  1. Crear un Config
  2. Utilice la configuración para crear un ScriptDirectory
  3. Utilice la Config y el ScriptDirectory para crear un AmbienteContexto
  4. Utilice el EnvironmentContext para crear un MigrationContext
  5. 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 la 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() entonces alembic stamp head (o código de pitón equivalente). Recomiendo usar siempre la ruta de migración para crear las tablas, e ignorar los datos en bruto metadata.create_all().

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.


2 para la respuesta № 3

Aquí hay un ejemplo puramente programático de cómo configurar y llamar comandos alambicos mediante programación.

La configuracion 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 Frasco-Alambique. 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 un cofre 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.