Migrations for ponyorm
This still a first beta version, but you can test it already with the following shell command:
$ pip install pony_upfrom pony_up import migrate
# to be able to bind the database with your information,
# just create a function for it:
def bind_func(db):
db.bind('database type', host='localhost', user='root', passwd='1234secure', db='test1')
# https://docs.ponyorm.com/api_reference.html#Database.bind
db.generate_mapping(create_tables=True)
# https://docs.ponyorm.com/api_reference.html#Database.generate_mapping
# end def
db = migrate(bind_func, folder_path="examples/migrations, python_import="examples.migrations")In case the import in line 1 does not work, see #1 for a workaround.
The updates are applied as soon as migrate is called. It will return db, being the latest schema.
-
migrations/: The migrations are in herev{number}/: for example "v0"__init__.py: needs to import the model and also migrate if present.model.py: model of version{number}migrate.py: the script which updates from the model in this folder ({number}) to the next version ({number+1}).
v{number}.py:
A file is possible too, if it has the attributemodelwith a functionregister_database(callingmodel.register_database(db))
and optionally amigrateattribute with functiondo_update(it will callmigrate.do_update(db)).
However use only one of those, either the folder or the single file.
def register_database(db):In this function should be your
orm.Entitysubclasses.
Arguments:
db- The database to register entities to.
def do_update(db, old_db=None):Here you write code which changes stuff in the database to get from the current version/folder to the next one.
Arguments:
db- The latest schema.old_db- This can have 3 different types:pony.orm.DatabaseA database schema of the previous migration step (Would be v0 if we are at v1. See Fig 1),Trueif the provideddbdatabase is old, and this version didn't introduced a new schema. (See v2 in Fig 1)Noneif there was no previous step (The first migration, e.g. v0)
See above, or have a look at the example folder.
Please do!
Report issues, suggest features, or even submit code!
Before I have used the file database.py, to include all my objects,
and still like to use the existing import statements. I imported it like the following:
from database import {EntityName}or I have imported all the database entities with the wildcard import like:
from database import *You should move the entity definitions in
database.pyinto a migrations step (v0.modelperhaps), and replace the file content withdb = migrate(...), like seen above.
Now you can add the following lines after saiddb = migrate(...)part:# register the tables to this module __all__ = ["db"] for t_name, t_clazz in db.entities.items(): globals()[t_name] = t_clazz __all__.append(t_name) # end for
You need to deploy some sort of locking, because else two clients trying to modify the same tables would end in a disaster.
If you use postgres, you can use Advisory Locks. (Also see this blog post with examples).
Request a lock before thedb = migrate(...), and release it afterwards:import psycopg2 con = psycopg2.connect(host=POSTGRES_HOST, user=POSTGRES_USER, password=POSTGRES_PASSWORD, database=POSTGRES_DB) cur = con.cursor() # requesting database update lock cur.execute("SELECT pg_advisory_lock(85,80);") # update lock (ascii: 85,80 = UP) # run the migration db = migrate(...) # releasing lock after database update cur.execute("SELECT pg_advisory_unlock(85,80);") # update lock (ascii: 85,80 = UP) res = cur.fetchone() if not isinstance(res[0], bool) or not res[0]: # True = success # Fail => false or no bool raise ValueError("Could not release update lock, lock was not held (Advisory Lock 85,80)") # end if
Replace the
cur.execute("SELECT pg_advisory_lock(85,80);")part above with:# requesting database update lock cur.execute("SELECT pg_try_advisory_lock(85,80);") # update lock (ascii: 85,80 = UP) res = cur.fetchone() if not isinstance(res[0], bool) or not res[0]: # True = success # Fail => false or no bool raise ValueError("Currently already upgrading. (Advisory Lock 85,80)") # end ifWith that your script will raise an exception (and probably terminate) if the database is already being upgraded somewhere else.
Note: in a webserver (flask, django, ...) environment this is probably not wanted. Like, a Nginx server would keep running, and uWSGI would spam the log withno python application found, check your startup logs for errors.
Because of the library
Pony ORM, and this tool doingupdates!
Got it? Yeah, what a sick joke! Tell your Grandma, too! Also there is the verbto pony up, which is not really related.
Definitely Littlepip! (see Fallout: Equestria)
lel.