diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 0000000..6bd92d6 --- /dev/null +++ b/alembic.ini @@ -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 diff --git a/alembic/README b/alembic/README new file mode 100644 index 0000000..98e4f9c --- /dev/null +++ b/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/alembic/env.py b/alembic/env.py new file mode 100644 index 0000000..1db1ebb --- /dev/null +++ b/alembic/env.py @@ -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() + diff --git a/alembic/script.py.mako b/alembic/script.py.mako new file mode 100644 index 0000000..9570201 --- /dev/null +++ b/alembic/script.py.mako @@ -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"} diff --git a/alembic/versions/913dfa11ef_initial_tables.py b/alembic/versions/913dfa11ef_initial_tables.py new file mode 100644 index 0000000..4516aba --- /dev/null +++ b/alembic/versions/913dfa11ef_initial_tables.py @@ -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 ### diff --git a/mandaye/auth/authform.py b/mandaye/auth/authform.py index 86f1038..c23b430 100644 --- a/mandaye/auth/authform.py +++ b/mandaye/auth/authform.py @@ -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']) diff --git a/mandaye/backends/default.py b/mandaye/backends/default.py index d2baa1d..0d0d779 100644 --- a/mandaye/backends/default.py +++ b/mandaye/backends/default.py @@ -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 diff --git a/mandaye/backends/sql.py b/mandaye/backends/sql.py index c9150ba..29cd058 100644 --- a/mandaye/backends/sql.py +++ b/mandaye/backends/sql.py @@ -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 diff --git a/mandaye/config.py b/mandaye/config.py index 700f367..e975c82 100644 --- a/mandaye/config.py +++ b/mandaye/config.py @@ -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 = '' diff --git a/mandaye/migration/README b/mandaye/migration/README deleted file mode 100644 index 6218f8c..0000000 --- a/mandaye/migration/README +++ /dev/null @@ -1,4 +0,0 @@ -This is a database migration repository. - -More information at -http://code.google.com/p/sqlalchemy-migrate/ diff --git a/mandaye/migration/__init__.py b/mandaye/migration/__init__.py deleted file mode 100644 index 2f288d3..0000000 --- a/mandaye/migration/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# template repository default module diff --git a/mandaye/migration/manage.py b/mandaye/migration/manage.py deleted file mode 100644 index 39fa389..0000000 --- a/mandaye/migration/manage.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python -from migrate.versioning.shell import main - -if __name__ == '__main__': - main(debug='False') diff --git a/mandaye/migration/migrate.cfg b/mandaye/migration/migrate.cfg deleted file mode 100644 index 37fd42a..0000000 --- a/mandaye/migration/migrate.cfg +++ /dev/null @@ -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 diff --git a/mandaye/migration/versions/001_initial_schema.py b/mandaye/migration/versions/001_initial_schema.py deleted file mode 100644 index 5ae86c4..0000000 --- a/mandaye/migration/versions/001_initial_schema.py +++ /dev/null @@ -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 "" % (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 "" % (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 "" % (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) - diff --git a/mandaye/migration/versions/__init__.py b/mandaye/migration/versions/__init__.py deleted file mode 100644 index 507b5ff..0000000 --- a/mandaye/migration/versions/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# template repository default versions module diff --git a/mandaye/models.py b/mandaye/models.py index 8022e88..6dde999 100644 --- a/mandaye/models.py +++ b/mandaye/models.py @@ -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 "" % (self.id, self.unique_id) diff --git a/requirements.txt b/requirements.txt index 24d1765..560d2ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/scripts/mandaye_admin_sql.py b/scripts/mandaye_admin_sql.py index eec567e..3931544 100755 --- a/scripts/mandaye_admin_sql.py +++ b/scripts/mandaye_admin_sql.py @@ -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 diff --git a/setup.py b/setup.py index 8d5b400..5c25a28 100644 --- a/setup.py +++ b/setup.py @@ -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',