backend: fixes backends import and sql backend

Migrate from sqlalchemy-migrate to alembic

 * mandaye/auth/authform.py: fix backend sql call
 * mandaye/backends/sql.py: fixes
 * mandaye/backends/default.py: fix backend call
 * mandaye/config.py: new backend call
This commit is contained in:
Jérôme Schneider 2013-05-22 11:40:48 +02:00
parent b8708efc97
commit e6211639e3
19 changed files with 249 additions and 184 deletions

51
alembic.ini Normal file
View File

@ -0,0 +1,51 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = alembic
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
revision_environment = true
# Automatically set from mandaye config
sqlalchemy.url = driver://user:pass@localhost/dbname
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

1
alembic/README Normal file
View File

@ -0,0 +1 @@
Generic single-database configuration.

74
alembic/env.py Normal file
View File

@ -0,0 +1,74 @@
from __future__ import with_statement
from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
from mandaye.models import Base
target_metadata = Base.metadata
# Import db url form mandaye
from mandaye.config import db_url
config.set_main_option("sqlalchemy.url", db_url)
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(url=url)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
engine = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool)
connection = engine.connect()
context.configure(
connection=connection,
target_metadata=target_metadata
)
try:
with context.begin_transaction():
context.run_migrations()
finally:
connection.close()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

22
alembic/script.py.mako Normal file
View File

@ -0,0 +1,22 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision}
Create Date: ${create_date}
"""
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

@ -0,0 +1,52 @@
"""Initial tables
Revision ID: 913dfa11ef
Revises: None
Create Date: 2013-05-22 11:31:22.676232
"""
# revision identifiers, used by Alembic.
revision = '913dfa11ef'
down_revision = None
from alembic import op
import sqlalchemy as sa
from mandaye.models import JSONEncodedDict
def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.create_table('idp_user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('unique_id', sa.String(length=150), nullable=False),
sa.Column('idp_id', sa.String(length=150), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_table('service_provider',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=50), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('sp_user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('login', sa.String(length=150), nullable=False),
sa.Column('post_values', JSONEncodedDict(), nullable=False),
sa.Column('creation_date', sa.DateTime(), nullable=False),
sa.Column('last_connection', sa.DateTime(), nullable=True),
sa.Column('idp_user_id', sa.Integer(), nullable=True),
sa.Column('service_provider_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['idp_user_id'], ['idp_user.id'], ),
sa.ForeignKeyConstraint(['service_provider_id'], ['service_provider.id'], ),
sa.PrimaryKeyConstraint('id')
)
### end Alembic commands ###
def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_table('sp_user')
op.drop_table('service_provider')
op.drop_table('idp_user')
### end Alembic commands ###

View File

@ -23,7 +23,7 @@ from mandaye.response import _500, _302, _401
from mandaye.response import template_response
from mandaye.server import get_response
from mandaye.config import backend
from mandaye.backends.default import backend
try:
from Crypto.Cipher import AES
@ -188,6 +188,7 @@ a password_field key if you want to encode a password.")
unique_id: idp uinique id
post_values: dict with the post values
"""
logger.debug('AuthForm._save_association', 'Save a new association')
sp_login = post_values[self.form_values['username_field']]
if config.encrypt_sp_password:
self._encrypt_pwd(post_values)
@ -275,8 +276,8 @@ a password_field key if you want to encode a password.")
logger.debug('User %s successfully login' % env['beaker.session']['unique_id'])
idp_user = backend.ManagerIDPUser.get(unique_id)
service_provider = backend.ManagerServiceProvider.get(self.site_name)
idp_user = backend.ManagerIDPUser.get_or_create(unique_id)
service_provider = backend.ManagerServiceProvider.get_or_create(self.site_name)
sp_user = backend.ManagerSPUser.get_last_connected(idp_user, service_provider)
if not sp_user:
logger.debug('User %s is not associate' % env['beaker.session']['unique_id'])

View File

@ -1,4 +1,9 @@
from importlib import import_module
from mandaye.config import storage_backend
from mandaye.exceptions import ImproperlyConfigured
class DefaultManagerIDPUser:
@staticmethod
@ -73,4 +78,15 @@ class DefaultServiceProvider:
def save(service_provider):
pass
def import_backend(path):
try:
mod = import_module(path)
except ImportError, e:
raise ImproperlyConfigured('Error importing backend %s: "%s"' % (path, e))
return mod
backend = import_backend(storage_backend)
ManagerServiceProvider = backend.ManagerServiceProvider
ManagerIDPUser = backend.ManagerIDPUser
ManagerSPUser = backend.ManagerSPUser

View File

@ -2,6 +2,7 @@
from datetime import datetime
from mandaye.db import sql_session
from mandaye.log import logger
from mandaye.models import IDPUser, SPUser, ServiceProvider
class ManagerIDPUserSQL:
@ -10,18 +11,19 @@ class ManagerIDPUserSQL:
def get(unique_id, idp_id='default'):
idp_user = sql_session().query(IDPUser).\
filter_by(unique_id=unique_id,
idp_id='default')
idp_id='default').all()
if len(idp_user) > 1:
logger.critical('ManagerIDPUserSQL.get %s not unique' % unique_id)
raise MandayeException(
'ManagerIDPUserSQL.get : %s is not unique' % unique_id)
if idp_user:
return idp_user.first()
return idp_user[0]
else:
return None
@staticmethod
def create(unique_id, idp_id='default'):
logger.info('Add idp user %s in db' % (unique_id))
idp_user = IDPUser(
unique_id=unique_id,
idp_id=idp_id)
@ -30,13 +32,15 @@ class ManagerIDPUserSQL:
@staticmethod
def get_or_create(unique_id, idp_id='default'):
if ManagerIDPUserSQL.get(**kwargs):
return user
idp_user= ManagerIDPUserSQL.get(unique_id, idp_id)
if idp_user:
return idp_user
else:
return ManagerIDPUserSQL.create(**kwargs)
return ManagerIDPUserSQL.create(unique_id, idp_id)
@staticmethod
def delete(idp_user):
logger.info('Delete in db idp user %s' % idp_user.unique_id)
sql_session().delete(idp_user)
sql_session().commit()
@ -48,7 +52,7 @@ class ManagerSPUserSQL:
@staticmethod
def get(login, idp_user, service_provider):
sp_user = sql_session().query(SPPUser).\
sp_user = sql_session().query(SPUser).\
join(IDPUser).\
join(ServiceProvider).\
filter_by(login=login,
@ -66,11 +70,10 @@ class ManagerSPUserSQL:
@staticmethod
def get_last_connected(idp_user, service_provider):
return sql_session().query(SPPUser).\
join(IDPUser).\
join(ServiceProvider).\
filter(idp_user=idp_user).\
filer(service_provider=service_provider).\
print idp_user
return sql_session().query(SPUser).\
filter(SPUser.idp_user==idp_user).\
filter(SPUser.service_provider==service_provider).\
order_by(SPUser.last_connection.desc()).\
first()
@staticmethod
@ -95,6 +98,7 @@ class ManagerSPUserSQL:
(login, idp_user.unique_id, service_provider.name))
sql_session().add(sp_user)
sql_session().commit()
logger.debug('New SP user %s in db' % (login))
return idp_user
@staticmethod
@ -132,7 +136,7 @@ class ManagerServiceProviderSQL:
@staticmethod
def create(name):
logger.info('Add %s service provider into the database' % name)
logger.info('Add %s service provider into the db' % name)
sp = ServiceProvider(name=name)
sql_session().add(sp)
sql_session().commit()
@ -157,6 +161,6 @@ class ManagerServiceProviderSQL:
sql_session().commit()
ManagerServiceProvider = ManagerServiceProviderSQL
ManagerIDPUser = ManagerIDPUserSQL
ManagerSPUser = ManagerSPUserSQL
ManagerServiceProvider = ManagerServiceProviderSQL

View File

@ -1,14 +1,16 @@
import logging
from mandaye.exceptions import ImproperlyConfigured
# Choose storage
# Only mandaye.backends.sql at the moment
storage_backend = "mandaye.backends.sql"
## SQL Backend config
# Database configuration
# rfc 1738 http://rfc.net/rfc1738.html
db_url = 'sqlite:///test.db'
# Default backend
import mandaye.backends.sql
backend = mandaye.backends.sql
# Needed if ssl is activated
ssl = False
keyfile = ''

View File

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

View File

@ -1 +0,0 @@
# template repository default module

View File

@ -1,5 +0,0 @@
#!/usr/bin/env python
from migrate.versioning.shell import main
if __name__ == '__main__':
main(debug='False')

View File

@ -1,25 +0,0 @@
[db_settings]
# Used to identify which repository this database is versioned under.
# You can use the name of your project.
repository_id=Mandaye migrations
# 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=[]
# When creating new change scripts, Migrate will stamp the new script with
# a version number. By default this is latest_version + 1. You can set this
# to 'true' to tell Migrate to use the UTC timestamp instead.
use_timestamp_numbering=False

View File

@ -1,118 +0,0 @@
import collections
import json
from datetime import datetime
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy import ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.mutable import Mutable
from sqlalchemy.orm import column_property, relationship, backref
from sqlalchemy.types import TypeDecorator, VARCHAR
Base = declarative_base()
class JSONEncodedDict(TypeDecorator):
"Represents an immutable structure as a json-encoded string."
impl = VARCHAR
def process_bind_param(self, value, dialect):
if value is not None:
value = json.dumps(value)
return value
def process_result_value(self, value, dialect):
if value is not None:
value = json.loads(value)
return value
class MutationDict(Mutable, dict):
@classmethod
def coerce(cls, key, value):
""" Convert plain dictionaries to MutationDict. """
if not isinstance(value, MutationDict):
if isinstance(value, dict):
return MutationDict(value)
# this call will raise ValueError
return Mutable.coerce(key, value)
else:
return value
def __setitem__(self, key, value):
""" Detect dictionary set events and emit change events. """
dict.__setitem__(self, key, value)
self.changed()
def __delitem__(self, key):
""" Detect dictionary del events and emit change events. """
dict.__delitem__(self, key)
self.changed()
MutationDict.associate_with(JSONEncodedDict)
class ServiceProvider(Base):
__tablename__ = 'service_provider'
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=True, nullable=False)
def __init__(self, name):
self.name = name
def __repr__(self):
return "<ServiceProvider('%s')>" % (self.name)
class IDPUser(Base):
__tablename__ = 'idp_user'
id = Column(Integer, primary_key=True)
# Nameid, pseudo, email, ...
unique_id = Column(String(150), nullable=False)
# Entityid
idp_id = Column(String(150), nullable=False)
sp_users = relationship("SPUser", backref=backref('idp_user'))
def __init__(self, unique_id=None, idp_id=None, sp_users=None):
self.unique_id = unique_id
self.idp_id = idp_id
self.sp_users = sp_users
def __repr__(self):
return "<IDPUser %d '%s'>" % (self.id, self.unique_id)
class SPUser(Base):
__tablename__ = 'sp_user'
id = Column(Integer, primary_key=True)
post_values = Column(JSONEncodedDict, nullable=False)
creation_date = Column(DateTime, default=datetime.now(), nullable=False)
last_connection = Column(DateTime, default=datetime.now())
idp_user_id = Column(Integer, ForeignKey('idp_user.id'))
service_provider_id = Column(Integer, ForeignKey('service_provider.id'),
nullable=False)
service_provider = relationship("ServiceProvider", backref=backref('users'))
def __init__(self, post_values=None):
self.post_values = post_values
def __repr__(self):
return "<SPUser '%d'>" % (self.id)
def upgrade(migrate_engine):
ServiceProvider.__table__.create(migrate_engine)
SPUser.__table__.create(migrate_engine)
IDPUser.__table__.create(migrate_engine)
def downgrade(migrate_engine):
ServiceProvider.__table__.drop(migrate_engine)
SPUser.__table__.drop(migrate_engine)
IDPUser.__table__.drop(migrate_engine)

View File

@ -1 +0,0 @@
# template repository default versions module

View File

@ -78,10 +78,9 @@ class IDPUser(Base):
idp_id = Column(String(150), nullable=False)
sp_users = relationship("SPUser", backref=backref('idp_user'))
def __init__(self, unique_id=None, idp_id=None, sp_users=None):
def __init__(self, unique_id=None, idp_id=None):
self.unique_id = unique_id
self.idp_id = idp_id
self.sp_users = sp_users
def __repr__(self):
return "<IDPUser %d '%s'>" % (self.id, self.unique_id)

View File

@ -4,6 +4,6 @@ pycrypto>=2.0
lxml>=2.0
xtraceback>=0.3
sqlalchemy>=0.7,<0.8
sqlalchemy-migrate>=0.7
alembic >= 0.5.0
Mako>=0.4
static

View File

@ -57,12 +57,10 @@ def main():
if options.createdb:
logger.info("Creating database...")
if config.db_url:
import migrate.versioning.api
import mandaye.migration
migrate.versioning.api.version_control(url=config.db_url,
repository=mandaye.migration.__path__[0])
migrate.versioning.api.upgrade(url=config.db_url,
repository=mandaye.migration.__path__[0])
from alembic.config import Config
from alembic import command
alembic_cfg = Config("alembic.ini")
command.upgrade(alembic_cfg, "head")
logger.info("Database created")
if options.cryptpwd:
from mandaye.config.backend import ManagerSPUser

View File

@ -16,7 +16,6 @@ install_requires=[
'poster>=0.8',
'pycrypto>=2.0',
'sqlalchemy>=0.6',
'sqlalchemy-migrate>=0.7.2',
'lxml>=2.0',
'xtraceback>=0.3',
'static',