J'utilise SQLite comme format de fichier d'application (voir ici pour savoir pourquoi vous souhaitez le faire) pour mon application de bureau basée sur PySide. Autrement dit, lorsqu'un utilisateur utilise mon application, ses données sont enregistrées dans un seul fichier de base de données sur sa machine. J'utilise l'ORM SQLAlchemy pour communiquer avec les bases de données.
Lorsque je publie de nouvelles versions de l'application, je peux modifier le schéma de la base de données. Je ne veux pas que les utilisateurs doivent jeter leurs données à chaque fois que je modifie le schéma, j'ai donc besoin de migrer leurs bases de données vers le format le plus récent. De plus, je crée beaucoup de bases de données temporaires pour enregistrer des sous-ensembles de données à utiliser avec certains processus externes. Je veux créer ces bases de données avec alambic afin qu'elles soient étiquetées avec la bonne version.
J'ai quelques questions:
Existe-t-il un moyen d'appeler alambic depuis mon Python? Je pense que c'est bizarre d'avoir à utiliser Popen
vers un pur Python , mais les documents utilisent simplement alembic à partir de la ligne de commande. Je dois principalement modifier l'emplacement de la base de données à l'emplacement de la base de données de l'utilisateur.
Si ce n'est pas possible, puis-je spécifier un nouvel emplacement de base de données à partir de la ligne de commande sans modifier le fichier .ini? Ainsi, appeler alambic via Popen
ne serait pas un gros problème.
Je vois que alembic conserve ses informations de version sous une simple table appelée alembic_version
, avec une colonne appelée version_num
et une seule ligne spécifiant la version. Puis-je ajouter un alembic_version
table à mon schéma et remplissez-la avec la dernière version lorsque je crée de nouvelles bases de données afin qu'il n'y ait pas de surcharge? Est-ce même une bonne idée; dois-je utiliser alambic pour créer toutes les bases de données?
J'ai un alambic qui fonctionne très bien pour la base de données unique que j'utilise pour développer avec dans le répertoire de mon projet. Je veux utiliser alembic pour migrer et créer facilement des bases de données dans des emplacements arbitraires, de préférence via une sorte d'API Python, et non la ligne de commande. Cette application est également gelée avec cx_Freeze, au cas où cela rendrait une différence.
Merci!
Voici ce que j'ai appris après avoir connecté mon logiciel à alembic
:
Oui. Au moment de la rédaction de cet article, le principal point d'entrée pour l'alambic est alembic.config.main
, vous pouvez donc l'importer et l'appeler vous-même, par exemple:
import alembic.config
alembicArgs = [
'--raiseerr',
'upgrade', 'head',
]
alembic.config.main(argv=alembicArgs)
Notez que alembic recherche les migrations dans le répertoire courant (c'est-à-dire os.getcwd ()). J'ai géré cela en utilisant os.chdir(migration_directory)
avant d'appeler alembic, mais il peut y avoir une meilleure solution.
Oui. La clé réside dans l'argument de ligne de commande -x
. Depuis alembic -h
(Étonnamment, je n'ai pas pu trouver de référence d'argument de ligne de commande dans les documents):
optional arguments:
-x X Additional arguments consumed by custom env.py
scripts, e.g. -x setting1=somesetting -x
setting2=somesetting
Vous pouvez donc créer votre propre paramètre, par ex. dbPath
, puis interceptez-le dans env.py
:
alembic -x dbPath=/path/to/sqlite.db upgrade head
puis par exemple dans 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
Bien sûr, vous pouvez également fournir le paramètre -x en utilisant argv
dans alembic.config.main
.
Je suis d'accord avec @ davidism sur l'utilisation des migrations vs metadata.create_all()
:)
C'est une question très large, et la mise en œuvre de votre idée dépendra de vous, mais c'est possible.
Vous pouvez appeler Alembic à partir de votre code Python sans utiliser les commandes, car il est également implémenté dans Python! Vous avez juste besoin de recréer ce que les commandes font en arrière-plan.
Certes, les documents ne sont pas en très bonne forme car ce sont encore des versions relativement tôt de la bibliothèque, mais avec un peu de fouille, vous trouverez ce qui suit:
J'ai écrit une extension pour fournir cet accès Alembic programmatique à une base de données Flask-SQLAlchemy. L'implémentation est liée à Flask et Flask-SQLAlchemy, mais cela devrait être un bon point de départ. Voir Flask-Alembic ici.
En ce qui concerne votre dernier point sur la façon de créer de nouvelles bases de données, vous pouvez soit utiliser Alembic pour créer les tables, soit utiliser metadata.create_all()
puis alembic stamp head
(Ou un code python équivalent _ ). Je recommande de toujours utiliser le chemin de migration pour créer les tables et d'ignorer la metadata.create_all()
brute.
Je n'ai aucune expérience avec cx_freeze, mais ça devrait aller tant que les migrations sont incluses dans la distribution et que le chemin vers ce répertoire dans le code est correct.
Si vous regardez la page API de commandes des documents alembic, vous voyez un exemple de la façon d'exécuter les commandes CLI directement à partir d'une application Python. Sans passer par l'interface CLI code.
Fonctionnement alembic.config.main
a l'inconvénient que le env.py
le script est exécuté, ce qui n'est peut-être pas ce que vous voulez. Par exemple, cela modifiera votre configuration de journalisation.
Un autre moyen très simple consiste à utiliser l '"API de commande" liée ci-dessus. Par exemple, voici une petite fonction d'aide que j'ai fini par écrire:
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')
J'utilise le set_main_option
méthode ici pour pouvoir exécuter les migrations sur une autre base de données si nécessaire. Je peux donc simplement appeler cela comme suit:
run_migrations('/path/to/migrations', 'postgresql:///my_database')
L'origine de ces deux valeurs (chemin et DSN) dépend de vous. Mais cela semble être très proche de ce que vous voulez réaliser. L'API de commandes possède également les méthodes stamp () qui vous permettent de marquer une base de données donnée comme étant d'une version spécifique. L'exemple ci-dessus peut être facilement adapté pour appeler cela.
Voici un exemple purement programmatique de la façon de configurer et d'appeler des commandes alembic par programmation.
La configuration du répertoire (pour faciliter la lecture du code)
. # root dir
|- alembic/ # directory with migrations
|- tests/diy_alembic.py # example script
|- alembic.ini # ini file
Et voici 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)
Le code est plus ou moins une coupure de ce fichier Flask-Alembic . C'est un bon endroit pour regarder l'utilisation et les détails des autres commandes.
Pourquoi cette solution? - Elle a été écrite dans le besoin de créer un tampon alambic, des mises à niveau et des déclassements lors de l'exécution de tests automatisés.
Je n'utilise pas Flask donc je ne pouvais pas utiliser la bibliothèque Flask-Alembic qui était déjà recommandée. Au lieu de cela après un peu de bricolage, j'ai codé la fonction courte suivante pour exécuter tout des migrations applicables. Je garde tous mes fichiers liés à l'alambic sous un sous-module (dossier) appelé migrations. Je garde en fait le alembic.ini
avec la env.py
, ce qui est peut-être un peu peu orthodoxe. Voici un extrait de mon alembic.ini
fichier à ajuster pour cela:
[alembic]
script_location = .
Ensuite, j'ai ajouté le fichier suivant dans le même répertoire et je l'ai nommé run.py
. Mais partout où vous conservez vos scripts, il vous suffit de modifier le code ci-dessous pour pointer vers les bons chemins:
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")
Puis avec ça run.py
fichier en place, cela me permet de le faire dans mon code principal:
from mymodule.migrations.run import run_sql_migrations
run_sql_migrations()
Pour toute autre personne essayant d'obtenir un résultat de voie de migration avec SQLAlchemy, cela a fonctionné pour moi:
Ajoutez migration.py à votre projet:
from flask_alembic import Alembic
def migrate(app):
alembic = Alembic()
alembic.init_app(app)
with app.app_context():
alembic.upgrade()
Appelez-le au démarrage de l'application après l'initialisation de votre base de données
application = Flask(__name__)
db = SQLAlchemy()
db.init_app(application)
migration.migrate(application)
Ensuite, il vous suffit de faire le reste des étapes d'alambic standard:
Initialisez votre projet en tant qu'alambic
alembic init alembic
Mettre à jour env.py:
from models import MyModel
target_metadata = [MyModel.Base.metadata]
Mettre à jour alembic.ini
sqlalchemy.url = postgresql://postgres:postgres@localhost:5432/my_db
En supposant que vos modèles SQLAlchemy sont déjà définis, vous pouvez générer automatiquement vos scripts maintenant:
alembic revision --autogenerate -m "descriptive migration message"
Si vous obtenez une erreur sur l'impossibilité d'importer votre modèle dans env.py, vous pouvez exécuter ce qui suit dans votre terminal pour corriger
export PYTHONPATH=/path/to/your/project
Enfin, mes scripts de migration étaient générés dans le répertoire alembic/versions, et j'ai dû les copier dans le répertoire migrations pour que alembic les récupère.
├── 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
J'ai probablement quelque chose de mal configuré, mais cela fonctionne maintenant.
Voir la documentation de alembic.operations.base.Operations:
from alembic.runtime.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)