NOTES
Vragen:
-
Inleiding¶
Flaskr¶
We werken verder met de Flaskr applicatie uit de vorige lessen. Daar hebben we SQLAlchemy ORM gebruikt om op een declaratieve manier user en post tables te definiëren met een aantal eenvoudige kolommen.
Ter herinneren, voor de user table hebben we het volgende model:
| flaskr/models.py | |
|---|---|
De applicatie werkt perfect maar op een dag beslis jij als ontwikkelaar (of je opdrachtgever) om de applicatie verder uit te breiden.
Voor elke User moet nu een email adres worden bewaard.
Geen probleem, we breiden de User class gewoon uit...
Alleen ... hoe zorgen we er voor dat de (elke!) database ook werkelijk deze nieuwe kolom krijgt? Als we Base.metadata.create_all(engine) uitvoeren gebeurt er niets van de tabel bestaat al. Eerste Base.metadata.drop_all(engine) uitvoeren is al zeker geen optie want dan zijn we alle huidige data kwijt.
Precies om dit probleem op te lossen is Alembic gemaakt.
Alembic is een migratie-tool voor SQLAlchemy die helpt om wijzigingen in je database-schema (zoals nieuwe tabellen, kolommen of constraints) op een gecontroleerde en herhaalbare manier door te voeren.
In plaats van manueel SQL-scripts te schrijven, gebruik je Alembic om migraties te genereren, beheren en toepassen via versiebeheer. Zo blijft je database consistent met de evolutie van je Python-modellen in SQLAlchemy.
Voorbereiding¶
Alembic¶
Alembic is een apart Python package en installeer je, bvb. met uv:
Migration Environment¶
Een migration environment in Alembic is de structuur en configuratie die Alembic nodig heeft om migraties uit te voeren binnen een project.
De basis-structuur van deze migration environment maak je eenmalig aan met het commando alembic init <directory>, waarbij als directory typisch alembic gebruikt wordt.
Je project krijgt dan de bvb. de volgende extra structuur:
yourproject/
alembic.ini
pyproject.toml
alembic/
env.py
README
script.py.mako
versions/
3512b954651e_add_account.py
2b1ae634e5cd_add_order_id.py
3adcc9a56557_rename_username_field.py
alembic.ini– Het hoofdconfiguratiebestand.alembic/env.py– Het script dat wordt uitgevoerd voor elke migratie.alembic/script.py.mako– Een template voor nieuwe migratiebestanden. Mako is een templating library (net als Jinja2!).alembic/versions/– Zal al de gegenereerde migratiebestanden bevatten.
Kort gezegd: het migratie environment is het technische kader waarin Alembic migraties kan aanmaken, bijhouden en toepassen — vergelijkbaar met een “projectconfiguratie” voor databaseversiebeheer.
Bekijk de aangemaakte directories en bestanden.
In alembic.ini moet de database URL geconfigureerd worden - op dezelfde manier als deze in SQLAlchemy gebruikt wordt.
Alembic en SQLite
Omdat SQLite beperkte ondersteuning heeft SQL ALTER commando's vormt het een speciaal geval voor Alembic.
Om niet onmiddellijk met een uitzondering te beginnen gebruiken we PostgreSQL als database.
Eerste Migratie: kolom toevoegen¶
We willen dus een age kolom toevoegen aan de user table. De eerste stap is om Alembic een nieuwe revisie de laten aanmaken met het alembic revision commando. Met de -m (message) optie wordt een beschrijving van de revisie meegegeven.
In de output zien we hoe het eerste migratiebestand wordt gemaakt, bvb. versions/ff3fc27cd408_add_user_email_column.py. De eerste 12 karakters zijn een unieke ID die Alembic zelf genereert.
Het bestand bevat:
- Standaard imports van
alembicalsop, gebruikt voor handelingen (operations) uit te voeren, zoals het toevoegen van een kolom.sqlalchemyalssa, gebruikt om elementen zoals een nieuwe kolom te definiëren, op dezelfde manier als hoe dit in SQLAlchemy zelf gebeurt.
- Identificatie van de vorige (
down_revision) en nieuwe revisie (revision). - Lege functies
upgrade()endowngrade().
Het is dan aan ons om deze functies in te vullen met de code die de gewenste wijzigingen in onze database zullen uitvoeren.
alembic.add_column voegt een nieuwe kolom toe. De argumenten zijn de naam van de tabel en de specificatie van de kolom. Daarvoor gebruiken we een sqlalchemy.Column object.
2. alembic.drop_column verwijdert een kolom. De argumenten zijn de naam van de tabel en de naam van de kolom.
Upgrade¶
Om te upgraden gebruik je het commando alembic upgrade met als argument de versie (revision) naar waar je wil upgraden. Met head upgrade je naar de laatste versie.
We controleren het resultaat:
Alembic heeft ook een nieuwe tabel toegevoegd, alembic_version met slechts één kolom en één rij. De waarde is het huidige versie (revisie) en komt overeen met de revisie in ons eerste migratiebestand.
Downgrade¶
Om terug te downgraden gebruik je het commando alembic downgrade met ook als argument de versie naar waar je wil downgrade. Met -1 downgrade je naar de vorige revisie. Op dezelfde manier kan +1 gebruikt worden met upgrade om terug naar de volgende revisie te gaan.
Tweede Migratie: data migratie¶
Gebruikers van de applicatie willen niet allemaal dat hun gebruikersnaam (username) bij hun posts verschijnt. We willen een nieuwe kolom, displayname, waar gebruikers zelf een alter-ego kunnen instellen. Het model wordt dan:
Om te starten genereren we een nieuwe Alembic revisie:
Naast het toevoegen van de nieuwe kolom willen we nu ook direct een initiële waarde toevoegen, zijnde username. De kolom moet ook not null zijn.
alembic.execute kunnen we een SQL commando uitvoeren om de initiële waarden van de nieuwe kolom in te vullen.
3. Daarna pas kunnen we de nieuwe kolom aanpassen met de NOT NULL constraint.
'user' in PostgreSQL
Bij het ontwerpen van het database model hebben we de ongelukkige keuze gemaakt de User tabel gewoon user te noemen. Maar deze naam is geserveerd in PostgreSQL. Daarom moeten we de tabelnaam quoten in het UPDATE SQL commando.
Andere alembic commando's¶
Autogenerate¶
Tot nu toe hebben we zelf de link gelegd tussen de aanpassingen in de SQLAlchemy modellen (mapped classes) en de nodige migratiestappen in Alembic.
Alembic kan dit echt ook (deels) automatisch doen met de autogenerate optie.
Hiervoor moet Alembic natuurlijk wel toegang krijgen tot de juiste SQLAlchemy Metadata. Dit doen we in het alembic/env.py script:
Voorbeeld¶
Als voorbeeld voegen we nogmaals een nieuwe kolom toe in User: de created kolom moet bijhouden wanneer een gebruiker zich geregistreerd heeft.
Het resultaat is een migratiebestand met volgende functies:
Wat kan Autogenerate?¶
Wel¶
- Table en Column toevoegen/verwijderen.
- (Non-)Nullable Column aanpassingen.
- Aanpassingen aan Indexes, Unique Constraints en Foreign Key Constraints.
- Column Type aanpassingen.
Niet¶
- Table name aanpassing. Wordt een drop+create. (!!)
- Column name aanpassing. Idem.
- Anonieme Constraints.
- Speciale SQLAlchemy types, bvb
Enum. - Sequence toevoegen/verwijderen.
Alembic in the praktijk¶
sqlalchemy.url beveiligen¶
Hetalembic.ini bestand gaat in version control en productie-databases zullen (hopelijk) een password nodig hebben.
De sqlalchemy.url kan dynamisch worden ingesteld vanuit het env.py script, bijvoorbeeld met behulp van environment variables.
Migratie detecteren¶
Met het alembic check commando kunnen we controleren of een nieuwe migratie revisie nodig is op basis van de huidige aanpassingen in de modellen.
Bvb.
Offline mode¶
Met offline mode wordt de migratie niet uitgevoerd maar in de plaats de exacte SQL commando's gegeneerd. Deze kunnen dan bvb. worden gecontroleerd (of zelfs uitgevoerd) door een database administrator (DBA) of specifiek team.
Omdat er geen verbinding wordt gemaakt met de database moeten we zowel de start als de eind revisie meegeven:
Naamgeving van Constraints¶
Zonder tussenkomst zal de naam van constraints gekozen worden door de database zelf.
Bvb.
Dit is vervelend want:
- Bij aanpassingen (migraties) moeten we de juiste naam gebruiken (bvb.
op.drop_constraint()). - Verschillende databases gebruiken verschillende (automatische) naamgeving.
Oplossing: vaste naming convention in SQLAlchemy/Alembic
| flaskr/models.py | |
|---|---|
Zowel SQLAlchemy als Alembic (autogenerate) zullen deze conventies nu gebruiken, ongeacht het type database. We kunnen dit uittesten met bvb. een in-memory SQLite database:
Migratie-script en Hooks¶
Met alembic init krijgen we het standaard migratie-script, env.py, dat gebruikt wordt tijdens de verschillende alembic commando's.
Dit script kan je verder aanpassen binnen je eigen projecten. We hebben dit al gebruikt om de sqlalchemy.url dynamisch in te stellen, en om de juiste SQLAlchemy Metadata mee te geven.
In env.py is context.configure() de centrale plek waar je instelt hoe een migratie uitgevoerd wordt.
Hooks zijn:
- Parameters waarmee Alembic extra logica kan uitvoeren tijdens het migratieproces
- Functies die je meegeeft aan
context.configure()
Een paar handiger voorbeelden wat hier mee mogelijk is:
include_object¶
Bvb. om (bepaalde) bestaande tables nooit te droppen.
Oplossing:
| alembic/env.py | |
|---|---|
process_revision_directives¶
Aangeroepen wanneer je een nieuwe migratie maakt met alembic revision --autogenerate.
Bvb. om lege migratie bestanden te vermijden.
| alembic/env.py | |
|---|---|