adding sqlalchemy-migrate glue

* add a product and producttype table (addresses #1)
 * add a person table and reference to customers table (fixes #8)
 * use sqlalchemy-migrate's API to setup database and add
   configuration for the sqlalchemy-migrate calls to development.ini
   and the paste_deploy template (fixes #7)


git-svn-id: file:///var/www/wwwusers/usr01/svn/pyalchemybiz/trunk@7 389c73d4-bf09-4d3d-a15e-f94a37d0667a
This commit is contained in:
Jan Dittberner 2008-10-05 21:02:10 +00:00
parent ab91d92af3
commit 1228fcef3c
16 changed files with 183 additions and 12 deletions

4
data/dbrepo/README Normal file
View file

@ -0,0 +1,4 @@
This is a database migration repository.
More information at
http://code.google.com/p/sqlalchemy-migrate/

0
data/dbrepo/__init__.py Normal file
View file

4
data/dbrepo/manage.py Normal file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env python
from migrate.versioning.shell import main
main(repository='data/dbrepo')

20
data/dbrepo/migrate.cfg Normal file
View file

@ -0,0 +1,20 @@
[db_settings]
# Used to identify which repository this database is versioned under.
# You can use the name of your project.
repository_id=pyalchemybiz
# The name of the database table used to track the schema version.
# This name shouldn't already be used by your project.
# If this is changed once a database is under version control, you'll need to
# change the table name in each database too.
version_table=migrate_version
# When committing a change script, Migrate will attempt to generate the
# sql for all supported databases; normally, if one of them fails - probably
# because you don't have that database installed - it is ignored and the
# commit continues, perhaps ending successfully.
# Databases in this list MUST compile successfully during a commit, or the
# entire commit will fail. List the databases your application will actually
# be using to ensure your updates to that database work properly.
# This must be a list; example: ['postgres','sqlite']
required_dbs=[]

View file

@ -0,0 +1,29 @@
from sqlalchemy import MetaData, Table, Column, ForeignKey, types
from migrate import *
def upgrade():
# Upgrade operations go here. Don't create your own engine; use the engine
# named 'migrate_engine' imported from migrate.
meta = MetaData(bind=migrate_engine)
t_product_type = Table(
'producttype', meta,
Column('id', types.Integer, primary_key=True),
Column('name', types.Unicode(40), nullable=False),
Column('description', types.UnicodeText(), nullable=False))
t_product_type.create()
t_product = Table(
'product', meta,
Column('id', types.Integer, primary_key=True),
Column('name', types.Unicode(100), nullable=False),
Column('description', types.UnicodeText(), nullable=False),
Column('producttype_id', types.Integer,
ForeignKey(t_product_type.c.id), nullable=False))
t_product.create()
def downgrade():
# Operations to reverse the above upgrade go here.
meta = MetaData(bind=migrate_engine)
t_product = Table('product', meta, autoload=True)
t_product.drop()
t_product_type = Table('product_type', meta, autoload=True)
t_product_type.drop()

View file

@ -0,0 +1,27 @@
from sqlalchemy import MetaData, Table, Column, ForeignKey, types
from migrate import *
def upgrade():
# Upgrade operations go here. Don't create your own engine; use the engine
# named 'migrate_engine' imported from migrate.
meta = MetaData(bind=migrate_engine)
t_person = Table(
'person', meta,
Column('id', types.Integer, primary_key=True),
Column('firstname', types.Unicode(100), nullable=False),
Column('lastname', types.Unicode(100), nullable=False))
t_person.create()
t_customer = Table(
'customer', meta,
Column('id', types.Integer, primary_key=True),
Column('person_id', types.Integer, ForeignKey(t_person.c.id),
nullable=False, unique=True))
t_customer.create()
def downgrade():
# Operations to reverse the above upgrade go here.
meta = MetaData(bind=migrate_engine)
t_customer = Table('customer', meta, autoload=True)
t_customer.drop()
t_person = Table('person', meta, autoload=True)
t_person.drop()

View file

View file

@ -40,6 +40,10 @@ sqlalchemy.default.url = sqlite:///%(here)s/pyalchemybiz.db
sqlalchemy.default.echo = true sqlalchemy.default.echo = true
sqlalchemy.convert_unicode = true sqlalchemy.convert_unicode = true
# settings for sqlalchemy-migrate
migrate.repo.version = 2
migrate.repo.dir = %(here)s/data/dbrepo
# Logging configuration # Logging configuration
[loggers] [loggers]

View file

@ -1,6 +1,6 @@
Metadata-Version: 1.0 Metadata-Version: 1.0
Name: pyalchemybiz Name: pyalchemybiz
Version: 0.1dev-r5 Version: 0.1dev-r6
Summary: python based small business suite. Summary: python based small business suite.
Home-page: http://www.dittberner.info/projects/pyalchemybiz Home-page: http://www.dittberner.info/projects/pyalchemybiz
Author: Jan Dittberner Author: Jan Dittberner

View file

@ -4,8 +4,13 @@ development.ini
setup.cfg setup.cfg
setup.py setup.py
test.ini test.ini
data/templates/base.mako.py data/dbrepo/README
data/templates/customer.mako.py data/dbrepo/__init__.py
data/dbrepo/manage.py
data/dbrepo/migrate.cfg
data/dbrepo/versions/001_Add_initial_tables.py
data/dbrepo/versions/002_Add_customer_tables.py
data/dbrepo/versions/__init__.py
docs/index.txt docs/index.txt
pyalchemybiz/__init__.py pyalchemybiz/__init__.py
pyalchemybiz/websetup.py pyalchemybiz/websetup.py
@ -24,6 +29,7 @@ pyalchemybiz/config/routing.py
pyalchemybiz/controllers/__init__.py pyalchemybiz/controllers/__init__.py
pyalchemybiz/controllers/customer.py pyalchemybiz/controllers/customer.py
pyalchemybiz/controllers/error.py pyalchemybiz/controllers/error.py
pyalchemybiz/controllers/index.py
pyalchemybiz/controllers/template.py pyalchemybiz/controllers/template.py
pyalchemybiz/lib/__init__.py pyalchemybiz/lib/__init__.py
pyalchemybiz/lib/app_globals.py pyalchemybiz/lib/app_globals.py
@ -32,10 +38,13 @@ pyalchemybiz/lib/helpers.py
pyalchemybiz/model/__init__.py pyalchemybiz/model/__init__.py
pyalchemybiz/model/customer.py pyalchemybiz/model/customer.py
pyalchemybiz/model/meta.py pyalchemybiz/model/meta.py
pyalchemybiz/public/index.html pyalchemybiz/model/person.py
pyalchemybiz/model/product.py
pyalchemybiz/templates/base.mako pyalchemybiz/templates/base.mako
pyalchemybiz/templates/customer.mako pyalchemybiz/templates/customer.mako
pyalchemybiz/templates/index.mako
pyalchemybiz/tests/__init__.py pyalchemybiz/tests/__init__.py
pyalchemybiz/tests/test_models.py pyalchemybiz/tests/test_models.py
pyalchemybiz/tests/functional/__init__.py pyalchemybiz/tests/functional/__init__.py
pyalchemybiz/tests/functional/test_customer.py pyalchemybiz/tests/functional/test_customer.py
pyalchemybiz/tests/functional/test_index.py

View file

@ -38,6 +38,11 @@ set debug = false
# invalidate the URI when specifying a SQLite db via path name # invalidate the URI when specifying a SQLite db via path name
#sqlalchemy.default.url = sqlite:///%(here)s/pyalchemybiz.db #sqlalchemy.default.url = sqlite:///%(here)s/pyalchemybiz.db
#sqlalchemy.default.echo = false #sqlalchemy.default.echo = false
sqlalchemy.default.convert_unicode = true
# settings for sqlalchemy-migrate
migrate.repo.version = 2
migrate.repo.dir = %(here)s/data/dbrepo
# Logging configuration # Logging configuration

View file

@ -3,6 +3,9 @@ import os
from pylons import config from pylons import config
from sqlalchemy import engine_from_config from sqlalchemy import engine_from_config
from sqlalchemy.exceptions import NoSuchTableError
from migrate.versioning.api import db_version
from pyalchemybiz.model import init_model from pyalchemybiz.model import init_model
import pyalchemybiz.lib.app_globals as app_globals import pyalchemybiz.lib.app_globals as app_globals
@ -35,4 +38,17 @@ def load_environment(global_conf, app_conf):
# any Pylons config options) # any Pylons config options)
engine = \ engine = \
engine_from_config(config, 'sqlalchemy.default.') engine_from_config(config, 'sqlalchemy.default.')
init_model(engine)
try:
init_model(engine)
except Exception:
# special handling for calls in websetup.py
import inspect
frame = inspect.currentframe()
try:
functions = [current[3] for current in \
inspect.getouterframes(frame)]
if functions[1] != 'setup_config':
raise
finally:
del frame

View file

@ -1,8 +1,11 @@
import logging
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy import orm from sqlalchemy import orm
from pyalchemybiz.model import meta from pyalchemybiz.model import meta
from pyalchemybiz.model import customer from pyalchemybiz.model import person, customer, product
log = logging.getLogger(__name__)
def init_model(engine): def init_model(engine):
"""Call me before using any of the tables or classes in the model.""" """Call me before using any of the tables or classes in the model."""
@ -11,7 +14,19 @@ def init_model(engine):
meta.engine = engine meta.engine = engine
meta.Session = orm.scoped_session(sm) meta.Session = orm.scoped_session(sm)
person.t_person = sa.Table(
'person', meta.metadata, autoload=True, autoload_with=engine)
customer.t_customer = sa.Table(
'customer', meta.metadata, autoload=True, autoload_with=engine)
product.t_producttype = sa.Table(
'producttype', meta.metadata, autoload=True, autoload_with=engine)
product.t_product = sa.Table(
'product', meta.metadata, autoload=True, autoload_with=engine)
customer.t_customer = sa.Table('customer', meta.metadata, orm.mapper(person.Person, person.t_person)
autoload=True, autoload_with=engine)
orm.mapper(customer.Customer, customer.t_customer) orm.mapper(customer.Customer, customer.t_customer)
customer.Customer.person = orm.relation(person.Person)
orm.mapper(product.ProductType, product.t_producttype)
orm.mapper(product.Product, product.t_product)
product.Product.producttype = orm.relation(
product.Product, backref=orm.backref('products', lazy='dynamic'))

View file

@ -0,0 +1,5 @@
t_person = None
class Person(object):
def __str__(self):
return "%s %s" % (self.firstname, self.lastname)

View file

@ -0,0 +1,8 @@
t_producttype = None
t_product = None
class ProductType(object):
pass
class Product(object):
pass

View file

@ -4,6 +4,10 @@ import logging
from paste.deploy import appconfig from paste.deploy import appconfig
from pylons import config from pylons import config
from sqlalchemy.exceptions import NoSuchTableError
import sys
from migrate.versioning.api import db_version, version_control, upgrade
from pyalchemybiz.config.environment import load_environment from pyalchemybiz.config.environment import load_environment
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -13,10 +17,31 @@ def setup_config(command, filename, section, vars):
conf = appconfig('config:' + filename) conf = appconfig('config:' + filename)
load_environment(conf.global_conf, conf.local_conf) load_environment(conf.global_conf, conf.local_conf)
repoversion = int(config.get('migrate.repo.version'))
repodir = config.get('migrate.repo.dir')
dburl = config.get('sqlalchemy.default.url')
# Populate the DB on 'paster setup-app' # Populate the DB on 'paster setup-app'
import pyalchemybiz.model as model
log.info("Setting up database connectivity...") log.info("Setting up database connectivity...")
log.info("Creating tables...") log.info("Desired database repository version: %d" % repoversion)
model.meta.metadata.create_all(bind=model.meta.engine) log.info("Desired database repository directory: %s" % repodir)
try:
dbversion = int(db_version(dburl, repodir))
except NoSuchTableError:
version_control(dburl, repodir)
dbversion = int(db_version(dburl, repodir))
except Exception, e:
log.error(e)
raise e
log.info("detected db version %d" % dbversion)
if dbversion < repoversion:
upgrade(dburl, repodir, repoversion)
elif dbversion > repoversion:
log.error("The database at %s is already versioned and its version " +
"%d is greater than the required version %d",
dburl, dbversion, repoversion)
sys.exit(1)
log.info("Successfully set up.") log.info("Successfully set up.")