|
| 1 | +# Migrations |
| 2 | + |
| 3 | +We will use `Alembic` to handle database schema changes. |
| 4 | + |
| 5 | +`SQLModel` is compatible with `Alembic`. |
| 6 | + |
| 7 | +## Initial example |
| 8 | + |
| 9 | +We'll continue from another example that has the creation of database and tables, and other essentials features. |
| 10 | + |
| 11 | +<details> |
| 12 | +<summary>👀 Full file example</summary> |
| 13 | + |
| 14 | +```Python |
| 15 | +{!./docs_src/advanced/migrations/tutorial001.py!} |
| 16 | +``` |
| 17 | + |
| 18 | +</details> |
| 19 | + |
| 20 | +## First step |
| 21 | + |
| 22 | +Add `Alembic` to your project. |
| 23 | + |
| 24 | +Example using pip. |
| 25 | + |
| 26 | +<div class="termy"> |
| 27 | + |
| 28 | +```console |
| 29 | +$ pip install alembic |
| 30 | + |
| 31 | +Installing collected packages: alembic |
| 32 | +Successfully installed alembic-1.8.1 |
| 33 | +``` |
| 34 | + |
| 35 | +</div> |
| 36 | + |
| 37 | +## Clean your code |
| 38 | + |
| 39 | +We need to clean our step that create the database and tables. |
| 40 | + |
| 41 | +```Python hl_lines="3-4" |
| 42 | +# Code above omitted 👆 |
| 43 | + |
| 44 | +{!./docs_src/advanced/migrations/tutorial001.py[ln:19-20]!} |
| 45 | + |
| 46 | +# Code below omitted 👇 |
| 47 | +``` |
| 48 | + |
| 49 | +```Python hl_lines="4-4" |
| 50 | +# Code above omitted 👆 |
| 51 | + |
| 52 | +{!./docs_src/advanced/migrations/tutorial001.py[ln:44-47]!} |
| 53 | + |
| 54 | +# Code below omitted 👇 |
| 55 | +``` |
| 56 | + |
| 57 | +<details> |
| 58 | +<summary>👀 Full file example</summary> |
| 59 | + |
| 60 | +```Python |
| 61 | +{!./docs_src/advanced/migrations/main.py!} |
| 62 | +``` |
| 63 | + |
| 64 | +</details> |
| 65 | + |
| 66 | +## Alembic configuration |
| 67 | + |
| 68 | +In this step we need initialize alembic. |
| 69 | + |
| 70 | +<div class="termy"> |
| 71 | + |
| 72 | +```console |
| 73 | +$ alembic init migrations |
| 74 | + |
| 75 | +Creating directory migrations ... done |
| 76 | +Creating directory migrations\versions ... done |
| 77 | +Generating alembic.ini ... done |
| 78 | +Generating migrations\env.py ... done |
| 79 | +Generating migrations\README ... done |
| 80 | +Generating migrations\script.py.mako ... done |
| 81 | +Please edit configuration/connection/logging settings in 'alembic.ini' before proceeding. |
| 82 | + |
| 83 | +``` |
| 84 | + |
| 85 | +</div> |
| 86 | + |
| 87 | +!!! info |
| 88 | + We can also use `alembic init alembic` to create `alembic` folder instead of `migrations` folder. |
| 89 | + |
| 90 | +Then go to `migrations\script.py.mako` to add sqlmodel module. |
| 91 | + |
| 92 | +```Python hl_lines="5-5" |
| 93 | +# Code above omitted 👆 |
| 94 | + |
| 95 | +{!./docs_src/advanced/migrations/tutorial003.py[ln:8-10]!} |
| 96 | + |
| 97 | +# Code below omitted 👇 |
| 98 | +``` |
| 99 | + |
| 100 | +!!! info |
| 101 | + In new migrations alembic will add SQLModel automatically. |
| 102 | + |
| 103 | +<details> |
| 104 | +<summary>👀 Full script.py.mako example</summary> |
| 105 | + |
| 106 | +```Python |
| 107 | +{!./docs_src/advanced/migrations/tutorial003.py!} |
| 108 | +``` |
| 109 | + |
| 110 | +</details> |
| 111 | + |
| 112 | +Then go to `migrations\env.py` to finish the alembic configuration. |
| 113 | + |
| 114 | +- Import your models (in this case `Hero`) and `SQLModel` |
| 115 | + |
| 116 | +```Python hl_lines="5-6" |
| 117 | +# Code above omitted 👆 |
| 118 | + |
| 119 | +{!./docs_src/advanced/migrations/tutorial004.py[ln:1-6]!} |
| 120 | + |
| 121 | +# Code below omitted 👇 |
| 122 | +``` |
| 123 | + |
| 124 | +!!! warning |
| 125 | + First import your models and then import SQLModel otherwise sqlmodel doesn´t recognize all models. |
| 126 | + |
| 127 | +- Then set your database url |
| 128 | + |
| 129 | +```Python hl_lines="4-4" |
| 130 | +# Code above omitted 👆 |
| 131 | + |
| 132 | +{!./docs_src/advanced/migrations/tutorial004.py[ln:13-14]!} |
| 133 | + |
| 134 | +# Code below omitted 👇 |
| 135 | +``` |
| 136 | + |
| 137 | +!!! tip |
| 138 | + This step can be replaced setting the same `sqlalchemy.url` variable in `alembic.ini` file. |
| 139 | + |
| 140 | +- Finally set `target_metadata` with your `SQLModel.metada` |
| 141 | + |
| 142 | +```Python hl_lines="3-3" |
| 143 | +# Code above omitted 👆 |
| 144 | + |
| 145 | +{!./docs_src/advanced/migrations/tutorial004.py[ln:25-25]!} |
| 146 | + |
| 147 | +# Code below omitted 👇 |
| 148 | +``` |
| 149 | + |
| 150 | +<details> |
| 151 | +<summary>👀 Full env.py example</summary> |
| 152 | + |
| 153 | +```Python |
| 154 | +{!./docs_src/advanced/migrations/tutorial004.py!} |
| 155 | +``` |
| 156 | + |
| 157 | +</details> |
| 158 | + |
| 159 | +## Run migrations |
| 160 | + |
| 161 | +In this step we need to generate the initial version of the database. |
| 162 | + |
| 163 | +<div class="termy"> |
| 164 | + |
| 165 | +```console |
| 166 | +$ alembic revision --autogenerate -m "init_db" |
| 167 | + |
| 168 | +INFO [alembic.runtime.migration] Context impl SQLiteImpl. |
| 169 | +INFO [alembic.runtime.migration] Will assume non-transactional DDL. |
| 170 | +INFO [alembic.autogenerate.compare] Detected added table 'hero' |
| 171 | +Generating migrations\versions\34abfb7ac266_init_db.py ... done |
| 172 | +``` |
| 173 | + |
| 174 | +</div> |
| 175 | + |
| 176 | +Now in `versions` folder we have a new file called `34abfb7ac266_init_db.py` |
| 177 | + |
| 178 | +!!! info |
| 179 | + This file has a revision id and the message part from our revision command. |
| 180 | + |
| 181 | +```{ .python .annotate } |
| 182 | +{!./docs_src/advanced/migrations/tutorial005.py!} |
| 183 | +``` |
| 184 | + |
| 185 | +{!./docs_src/advanced/migrations/annotations/en/tutorial005.md!} |
| 186 | + |
| 187 | +!!! success |
| 188 | + At this moment we have all the files to create our new database model. |
| 189 | + |
| 190 | +Initialize the database: |
| 191 | + |
| 192 | +<div class="termy"> |
| 193 | + |
| 194 | +```console |
| 195 | +$ alembic upgrade head |
| 196 | + |
| 197 | +INFO [alembic.runtime.migration] Context impl SQLiteImpl. |
| 198 | +INFO [alembic.runtime.migration] Will assume non-transactional DDL. |
| 199 | +INFO [alembic.runtime.migration] Running upgrade -> 34abfb7ac266, init_db |
| 200 | +``` |
| 201 | + |
| 202 | +</div> |
| 203 | + |
| 204 | +Now we have two tables: |
| 205 | + |
| 206 | +- `alembic_version`: with the version_num asociate with the revision id |
| 207 | +- `hero`: the new table from our model |
| 208 | + |
| 209 | +<img class="shadow" src="/img/advanced/migrations/migrations001.png"> |
| 210 | + |
| 211 | +`Hero` table is empty. |
| 212 | + |
| 213 | +<img class="shadow" src="/img/advanced/migrations/migrations002.png"> |
| 214 | + |
| 215 | +Then run `main.py` script |
| 216 | + |
| 217 | +<div class="termy"> |
| 218 | + |
| 219 | +```console |
| 220 | +$ python main.py |
| 221 | + |
| 222 | +INFO sqlalchemy.engine.Engine BEGIN (implicit) |
| 223 | +INFO sqlalchemy.engine.Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?) |
| 224 | +INFO sqlalchemy.engine.Engine [generated in 0.00035s] ('Deadpond', 'Dive Wilson', None) |
| 225 | +INFO sqlalchemy.engine.Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?) |
| 226 | +INFO sqlalchemy.engine.Engine [cached since 0.002439s ago] ('Spider-Boy', 'Pedro Parqueador', None) |
| 227 | +INFO sqlalchemy.engine.Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?) |
| 228 | +INFO sqlalchemy.engine.Engine [cached since 0.003134s ago] ('Rusty-Man', 'Tommy Sharp', 48) |
| 229 | +INFO sqlalchemy.engine.Engine COMMIT |
| 230 | +INFO sqlalchemy.engine.Engine BEGIN (implicit) |
| 231 | +INFO sqlalchemy.engine.Engine SELECT hero.id, hero.name, hero.secret_name, hero.age |
| 232 | +FROM hero |
| 233 | +INFO sqlalchemy.engine.Engine [generated in 0.00038s] () |
| 234 | +age=None id=1 name='Deadpond' secret_name='Dive Wilson' |
| 235 | +age=None id=2 name='Spider-Boy' secret_name='Pedro Parqueador' |
| 236 | +age=48 id=3 name='Rusty-Man' secret_name='Tommy Sharp' |
| 237 | +INFO sqlalchemy.engine.Engine ROLLBACK |
| 238 | +``` |
| 239 | + |
| 240 | +</div> |
| 241 | + |
| 242 | +Now the `hero` table has new rows: |
| 243 | + |
| 244 | +<img class="shadow" src="/img/advanced/migrations/migrations003.png"> |
| 245 | + |
| 246 | +## Next steps |
| 247 | + |
| 248 | +If we edit our model changing the database schema we can run again alembic to generate a new revision. |
| 249 | + |
| 250 | +Example: adding a new field named `power` |
| 251 | + |
| 252 | +```Python hl_lines="4-4" |
| 253 | +# Code above omitted 👆 |
| 254 | + |
| 255 | +{!./docs_src/advanced/migrations/tutorial006.py[ln:10-11]!} |
| 256 | + |
| 257 | +# Code below omitted 👇 |
| 258 | +``` |
| 259 | + |
| 260 | +<details> |
| 261 | +<summary>👀 Full file example</summary> |
| 262 | + |
| 263 | +```Python |
| 264 | +{!./docs_src/advanced/migrations/tutorial006.py!} |
| 265 | +``` |
| 266 | + |
| 267 | +</details> |
| 268 | + |
| 269 | +<div class="termy"> |
| 270 | + |
| 271 | +```console |
| 272 | +$ alembic revision --autogenerate -m "new field power" |
| 273 | + |
| 274 | +INFO [alembic.runtime.migration] Context impl SQLiteImpl. |
| 275 | +INFO [alembic.runtime.migration] Will assume non-transactional DDL. |
| 276 | +INFO [alembic.autogenerate.compare] Detected added column 'hero.power' |
| 277 | +Generating migrations\versions\b39b8d3c77f0_new_field_power.py ... done |
| 278 | +``` |
| 279 | + |
| 280 | +</div> |
| 281 | + |
| 282 | +The new file `b39b8d3c77f0_new_field_power.py`: |
| 283 | + |
| 284 | +```{ .python .annotate } |
| 285 | +{!./docs_src/advanced/migrations/tutorial007.py!} |
| 286 | +``` |
| 287 | + |
| 288 | +{!./docs_src/advanced/migrations/annotations/en/tutorial007.md!} |
| 289 | + |
| 290 | +!!! note |
| 291 | + Run `alembic upgrade head` to add the new field named power |
| 292 | + |
| 293 | +<div class="termy"> |
| 294 | + |
| 295 | +```console |
| 296 | +$ alembic upgrade head |
| 297 | + |
| 298 | +INFO [alembic.runtime.migration] Context impl SQLiteImpl. |
| 299 | +INFO [alembic.runtime.migration] Will assume non-transactional DDL. |
| 300 | +INFO [alembic.runtime.migration] Running upgrade 357d6ebcfadf -> b39b8d3c77f0, new field power |
| 301 | +``` |
| 302 | + |
| 303 | +</div> |
| 304 | + |
| 305 | +!!! note |
| 306 | + After you can downgrade the database to the previous version, run `alembic downgrade -1` |
| 307 | + |
| 308 | +<div class="termy"> |
| 309 | + |
| 310 | +```console |
| 311 | +$ alembic downgrade -1 |
| 312 | + |
| 313 | +INFO [alembic.runtime.migration] Context impl SQLiteImpl. |
| 314 | +INFO [alembic.runtime.migration] Will assume non-transactional DDL. |
| 315 | +INFO [alembic.runtime.migration] Running downgrade b39b8d3c77f0 -> 357d6ebcfadf, new field power |
| 316 | +``` |
| 317 | + |
| 318 | +</div> |
| 319 | + |
| 320 | +!!! success |
| 321 | + Migrations complete!!! Try adding new tables and relationship. |
0 commit comments