Changeset 3617

Show
Ignore:
Timestamp:
11/06/07 20:54:48 (1 year ago)
Author:
chrisz
Message:

Simplified and improved SQLAlchemy (0.3/0.4) and Elixir support. Fixes tickets #1458, #1599 and #1604.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • branches/1.0/turbogears/command/base.py

    r3446 r3617  
    175175        if config.get("sqlalchemy.dburi"): 
    176176            using_sqlalchemy = True 
    177             database.bind_meta_data() 
     177            database.get_engine() 
    178178            locals.update(session=database.session, 
    179179                          metadata=database.metadata) 
  • branches/1.0/turbogears/command/sacommand.py

    r3512 r3617  
    44try: 
    55    from sqlalchemy import MetaData, exceptions, Table, String 
    6     from turbogears.database import bind_meta_data, metadata, get_engine 
     6    from turbogears.database import metadata, get_engine 
    77except ImportError: 
    88    pass 
     
    2929def create(command, args): 
    3030    print "Creating tables at %s" % (config.get("sqlalchemy.dburi")) 
    31     bind_meta_data() 
     31    get_engine() 
    3232    get_model() 
    3333    metadata.create_all() 
     
    5252[sacommand.when("command == 'status'")] 
    5353def status(command, args): 
    54     bind_meta_data() 
     54    get_engine() 
    5555    get_model() 
    5656    ret = compare_metadata(metadata, MetaData(metadata.bind)) 
     
    7575                rc.append("Change table " + pyt.fullname) 
    7676                rc.extend(indent(ret) + ['']) 
    77     return rc                 
     77    return rc 
    7878 
    7979def compare_table(pyt, dbt): 
    80     rc = []     
     80    rc = [] 
    8181    dbcols = dict([(s.lower(), s) for s in dbt.columns.keys()]) 
    8282    for pyc in pyt.columns: 
     
    9292    for dbcol in dbcols: 
    9393        rc.append("Remove column " + dbcol) 
    94     return rc         
    95          
     94    return rc 
     95 
    9696def compare_column(pyc, dbc): 
    9797    rc = [] 
     
    105105            if pyc.type.length != dbc.type.length: 
    106106                rc.append('Change length to ' + str(pyc.type.length)) 
    107      
     107 
    108108    # Check primary key 
    109     if dbc.primary_key != pyc.primary_key:         
     109    if dbc.primary_key != pyc.primary_key: 
    110110        rc.append(pyc.primary_key and 'Make primary key' or 'Remove primary key') 
    111      
     111 
    112112    # Check foreign keys 
    113113    # TBD 
    114      
     114 
    115115    # Check default - disabled for now (didn't work on SQLite) 
    116116    #if dbc.default != pyc.default: 
    117117    #    rc.append('Change default to ' + str(pyc.default.arg)) 
    118      
     118 
    119119    # Check index - disabled for now (didn't work on SQLite) 
    120120    #if dbc.index != pyc.index: 
    121     #    rc.append(pyc.index and 'Add index' or 'Remove index')     
    122     
     121    #    rc.append(pyc.index and 'Add index' or 'Remove index') 
     122 
    123123    return rc 
  • branches/1.0/turbogears/database.py

    r3512 r3617  
    55import time 
    66import logging 
     7 
     8import cherrypy 
     9from cherrypy.filters.basefilter import BaseFilter 
     10 
     11try: 
     12    import sqlalchemy 
     13    from sqlalchemy.orm import create_session as orm_create_session 
     14except ImportError: 
     15    sqlalchemy = None 
    716 
    817try: 
     
    1322    sqlobject = None 
    1423 
    15 import cherrypy 
    16 from cherrypy.filters.basefilter import BaseFilter 
    17  
    1824import dispatch 
    1925from turbogears import config 
     
    2531_engine = None 
    2632 
    27 # Provide support for sqlalchemy 
    28 try: 
    29     import sqlalchemy 
    30     from sqlalchemy.ext import activemapper 
    31     from sqlalchemy.exceptions import InvalidRequestError 
    32  
     33# Provide support for SQLAlchemy 
     34if sqlalchemy: 
    3335    def get_engine(): 
    3436        """Retrieve the engine based on the current configuration.""" 
     
    3941                if "sqlalchemy" in k: 
    4042                    alch_args[k.split(".")[-1]] = v 
    41  
    4243            dburi = alch_args.pop('dburi') 
    4344            if not dburi: 
    4445                raise KeyError("No sqlalchemy database config found!") 
    45  
    4646            _engine = sqlalchemy.create_engine(dburi, **alch_args) 
     47        if not metadata.is_bound(): 
    4748            metadata.bind = _engine 
    48  
    49         elif not metadata.is_bound(): 
    50             metadata.bind = _engine 
    51  
    5249        return _engine 
    5350 
     
    5552        """Create a session that uses the engine from thread-local metadata.""" 
    5653        if not metadata.is_bound(): 
    57             bind_meta_data() 
    58  
    59         return sqlalchemy.orm.create_session() 
    60  
    61     metadata = activemapper.metadata 
    62     session = activemapper.Objectstore(create_session) 
    63     activemapper.objectstore = session 
    64  
    65     def bind_meta_data(): 
    66         get_engine() 
    67  
    68 except ImportError: 
    69     sqlalchemy = None 
    70  
     54            get_engine() 
     55        return orm_create_session() 
     56 
     57    metadata = sqlalchemy.MetaData() 
     58    try: 
     59        from sqlalchemy.orm import scoped_session 
     60        session = scoped_session(create_session) 
     61        mapper = session.mapper # use session-aware mapper 
     62    except ImportError: # SQLAlchemy < 0.4 
     63        from sqlalchemy.ext.sessioncontext import SessionContext 
     64        class Objectstore(object): 
     65            def __init__(self): 
     66                self.context = SessionContext(create_session) 
     67            def __getattr__(self, name): 
     68                return getattr(self.context.registry(), name) 
     69            session = property(lambda s: s.context.registry()) 
     70        session = Objectstore() 
     71        context = session.context 
     72        Query = sqlalchemy.Query 
     73        from sqlalchemy.orm import mapper as orm_mapper 
     74        def mapper(cls, *args, **kwargs): 
     75            validate = kwargs.pop('validate', False) 
     76            if not hasattr(getattr(cls, '__init__'), 'im_func'): 
     77                def __init__(self, **kwargs): 
     78                     for key, value in kwargs.items(): 
     79                         if validate and key not in self.mapper.props: 
     80                             raise KeyError( 
     81                                "Property does not exist: '%s'" % key) 
     82                         setattr(self, key, value) 
     83                cls.__init__ = __init__ 
     84            m = orm_mapper(cls, extension=context.mapper_extension, 
     85                *args, **kwargs) 
     86            class query_property(object): 
     87                def __get__(self, instance, cls): 
     88                    return Query(cls, session=context.current) 
     89            cls.query = query_property() 
     90            return m 
     91 
     92else: 
     93    def get_engine(): 
     94        pass 
     95 
     96    def create_session(): 
     97        pass 
     98 
     99    metadata = session = mapper = None 
    71100 
    72101try: 
     
    77106hub_registry = set() 
    78107 
    79 # This dictionary stores the AutoConnectHubs used for each 
    80 # connection URI 
    81 _hubs = dict() 
    82  
    83  
     108_hubs = dict() # stores the AutoConnectHubs used for each connection URI 
     109 
     110# Provide support for SQLObject 
    84111if sqlobject: 
    85112    def _mysql_timestamp_converter(raw): 
     
    397424    req.sa_transaction = make_sa_transaction(session) 
    398425 
    399  
    400426def sa_tr_active(tr): 
    401     if hasattr(session, 'context'):  
     427    if hasattr(session, 'context'): 
    402428        # SA 0.3 
    403429        return tr.session.transaction 
    404     else:  
     430    else: 
    405431        # SA 0.4 (effectively always active - commit or rollback don't fail) 
    406432        return True 
    407      
    408433 
    409434def make_sa_transaction(session): 
    410     if hasattr(session, 'context'):  
     435    if hasattr(session, 'context'): 
    411436        # SA 0.3 
    412437        tr = session.create_transaction() 
    413         return tr         
    414  
    415     else:  
    416         # SA 0.4         
     438        return tr 
     439 
     440    else: 
     441        # SA 0.4 
    417442        session.begin() 
    418443        return session 
    419  
    420444 
    421445def so_to_dict(sqlobj): 
     
    461485        end_all() 
    462486 
    463 __all__ = ["PackageHub", "AutoConnectHub", "set_db_uri", 
     487__all__ = ["get_engine", "metadata", "session", "mapper", 
     488           "PackageHub", "AutoConnectHub", "set_db_uri", 
    464489           "commit_all", "rollback_all", "end_all", "so_to_dict", 
    465490           "so_columns", "so_joins", "EndTransactionsFilter"] 
    466  
    467 if sqlalchemy: 
    468     __all__.extend(["metadata", "session", "bind_meta_data"]) 
  • branches/1.0/turbogears/identity/saprovider.py

    r3487 r3617  
    1 import cherrypy 
    2 import random 
    3 from datetime import * 
    4  
    5 import turbogears 
    6 from turbogears import identity 
     1from sqlalchemy.orm import class_mapper 
     2 
     3from turbogears import config, identity 
     4from turbogears.database import session 
    75from turbogears.util import load_class 
    8 from turbogears.database import session 
    96from turbojson.jsonify import * 
    10 from sqlalchemy.orm import class_mapper 
    117 
    128import logging 
     
    1915 
    2016# Global class references -- 
    21 # these will be set when the Provider is initialised. 
     17# these will be set when the provider is initialised. 
    2218user_class = None 
    2319group_class = None 
     
    4036        # Attempt to load the user. After this code executes, there *WILL* be 
    4137        # a _user attribute, even if the value is None. 
    42         visit = session.query(visit_class).filter_by( 
    43                 visit_key=self.visit_key).first() 
     38        visit = visit_class.query.filter_by(visit_key=self.visit_key).first() 
    4439        if not visit: 
    4540            self._user = None 
    4641            return None 
    47         self._user = session.query(user_class).get(visit.user_id) 
     42        self._user = user_class.query.get(visit.user_id) 
    4843        return self._user 
    4944    user = property(_get_user) 
     
    6964        else: 
    7065            self._permissions = frozenset([ 
    71                     p.permission_name for p in self.user.permissions]) 
     66                p.permission_name for p in self.user.permissions]) 
    7267        return self._permissions 
    7368    permissions = property(_get_permissions) 
     
    9388            return 
    9489        try: 
    95             visit = session.query(visit_class).filter_by( 
    96                     visit_key=self.visit_key).first() 
     90            visit = visit_class.query.filter_by(visit_key=self.visit_key).first() 
    9791            session.delete(visit) 
    9892            # Clear the current identity 
     
    112106    def __init__(self): 
    113107        super(SqlAlchemyIdentityProvider, self).__init__() 
    114         get=turbogears.config.get 
     108        get = config.get 
    115109 
    116110        global user_class, group_class, permission_class, visit_class 
     
    155149            permissions: a set of permission IDs 
    156150        ''' 
    157         user = session.query(user_class).filter_by(user_name=user_name).first() 
     151        user = user_class.query.filter_by(user_name=user_name).first() 
    158152        if not user: 
    159153            log.warning("No such user: %s", user_name) 
     
    166160                  visit_key) 
    167161        # Link the user to the visit 
    168         link = session.query(visit_class).filter_by( 
    169                 visit_key=visit_key).first() 
    170  
     162        link = visit_class.query.filter_by(visit_key=visit_key).first() 
    171163        if not link: 
    172164            link = visit_class() 
    173165            link.visit_key = visit_key 
    174166            link.user_id = user.user_id 
    175             session.save(link) 
    176  
    177167        else: 
    178168            link.user_id = user.user_id 
    179  
    180169        session.flush() 
    181170        return SqlAlchemyIdentity(visit_key, user) 
  • branches/1.0/turbogears/qstemplates/quickstart/+package+/model.py_tmpl

    r3515 r3617  
    1 #if $identity != "none" 
     1#if $identity != 'none' 
    22from datetime import datetime 
    33#end if 
     
    77#end if 
    88#if $sqlalchemy == 'True' 
    9 from sqlalchemy import (Table, Column, String, DateTime, Date, Integer, 
    10         DECIMAL, Unicode, ForeignKey, and_, or_) 
     9from sqlalchemy import * 
    1110#if $elixir == 'True' 
     11from turbogears.database import metadata, session 
     12import elixir 
     13elixir.metadata, elixir.session = metadata, session 
    1214from elixir import * 
    13 #end if 
    14 from turbogears.database import metadata, session 
    15 #if $elixir == 'False' 
    16 from sqlalchemy.orm import mapper, relation 
    17 #end if 
    18 #end if 
    19 #if $identity != "none" 
     15#else 
     16from turbogears.database import metadata, mapper 
     17from sqlalchemy.orm import relation 
     18#end if 
     19#end if 
     20#if $identity != 'none' 
    2021from turbogears import identity 
    2122#end if 
    2223 
    23 #if $sqlalchemy != "True" 
     24#if $sqlobject == 'True' 
    2425hub = PackageHub('${package}') 
    2526__connection__ = hub 
    2627 
     28# your data model 
     29 
    2730# class YourDataClass(SQLObject): 
    2831#     pass 
    2932#end if 
    30 #if $sqlalchemy == "True" 
    31 #if $elixir == "True" 
     33#if $sqlalchemy == 'True' 
     34#if $elixir == 'True' 
     35# your data model 
     36 
    3237# class YourDataClass(Entity): 
    3338#     pass 
    3439#else 
     40# your data tables 
     41 
    3542# your_table = Table('yourtable', metadata, 
    3643#     Column('my_id', Integer, primary_key=True) 
    3744# ) 
    3845 
     46# your model classes 
     47 
    3948# class YourDataClass(object): 
    4049#     pass 
    4150 
    42 # session.mapper(YourDataClass, your_table) 
    43 #end if 
    44 #end if 
    45  
    46 #if $identity == "sqlobject" 
    47 ### 
    48 # identity models. 
    49 ### 
     51# mapper(YourDataClass, your_table) 
     52#end if 
     53#end if 
     54 
     55#if $identity == 'sqlobject' 
     56 
     57# the identity model 
     58 
    5059class Visit(SQLObject): 
    5160    """ 
     
    152161                         otherColumn='group_id') 
    153162#end if 
    154 #if $identity == "sqlalchemy" and $elixir == "False" 
    155 # The identity schema. 
     163#if $identity == 'sqlalchemy' and $elixir != 'True' 
     164# the identity schema 
     165 
    156166visits_table = Table('visit', metadata, 
    157167    Column('visit_key', String(40), primary_key=True), 
     
    189199user_group_table = Table('user_group', metadata, 
    190200    Column('user_id', Integer, ForeignKey('tg_user.user_id', 
    191         onupdate="CASCADE", ondelete="CASCADE")), 
     201        onupdate='CASCADE', ondelete='CASCADE')), 
    192202    Column('group_id', Integer, ForeignKey('tg_group.group_id', 
    193         onupdate="CASCADE", ondelete="CASCADE")) 
     203        onupdate='CASCADE', ondelete='CASCADE')) 
    194204) 
    195205 
    196206group_permission_table = Table('group_permission', metadata, 
    197207    Column('group_id', Integer, ForeignKey('tg_group.group_id', 
    198         onupdate="CASCADE", ondelete="CASCADE")), 
     208        onupdate='CASCADE', ondelete='CASCADE')), 
    199209    Column('permission_id', Integer, ForeignKey('permission.permission_id', 
    200         onupdate="CASCADE", ondelete="CASCADE")) 
    201 
    202  
    203 
    204 # identity model 
    205 
     210        onupdate='CASCADE', ondelete='CASCADE')) 
     211
     212 
     213# the identity model 
     214 
    206215class Visit(object): 
    207216    """ 
     
    209218    """ 
    210219    def lookup_visit(cls, visit_key): 
    211         return session.query(Visit).get(visit_key) 
     220        return cls.query.get(visit_key) 
    212221    lookup_visit = classmethod(lookup_visit) 
    213222 
     
    226235class User(object): 
    227236    """ 
    228     Reasonably basic User definition. Probably would want additional 
    229     attributes. 
     237    Reasonably basic User definition. 
     238    Probably would want additional attributes. 
    230239    """ 
    231240    def permissions(self): 
     
    236245    permissions = property(permissions) 
    237246 
    238     def by_email_address(klass, email): 
     247    def by_email_address(cls, email): 
    239248        """ 
    240249        A class method that can be used to search users 
    241250        based on their email addresses since it is unique. 
    242251        """ 
    243         return session.query(klass).filter_by(email_address=email).first() 
     252        return cls.query.filter_by(email_address=email).first() 
    244253 
    245254    by_email_address = classmethod(by_email_address) 
    246255 
    247     def by_user_name(klass, username): 
     256    def by_user_name(cls, username): 
    248257        """ 
    249258        A class method that permits to search users 
    250259        based on their user_name attribute. 
    251260        """ 
    252         return session.query(klass).filter_by(user_name=username).first() 
     261        return cls.query.filter_by(user_name=username).first() 
    253262 
    254263    by_user_name = classmethod(by_user_name) 
     
    291300                secondary=group_permission_table, backref='permissions'))) 
    292301#end if 
    293 #if $identity == "sqlalchemy" and $elixir == "True" 
     302#if $identity == 'sqlalchemy' and $elixir == 'True' 
    294303# 
    295 # identity model 
     304# the identity model 
    296305# 
    297306class Visit(Entity): 
     307    """ 
     308    A visit to your site 
     309    """ 
    298310    has_field('visit_key', String(40), primary_key=True) 
    299311    has_field('created', DateTime, nullable=False, default=datetime.now) 
     
    306318 
    307319class VisitIdentity(Entity): 
     320    """ 
     321    A Visit that is link to a User object 
     322    """ 
    308323    has_field('visit_key', String(40), primary_key=True) 
    309324    belongs_to('user', of_kind='User', colname='user_id', use_alter=True) 
     
    311326 
    312327class Group(Entity): 
     328    """ 
     329    An ultra-simple group definition. 
     330    """ 
    313331    has_field('group_id', Integer, primary_key=True) 
    314332    has_field('group_name', Unicode(16), unique=True) 
     
    320338 
    321339class User(Entity): 
     340    """ 
     341    Reasonably basic User definition. 
     342    Probably would want additional attributes. 
     343    """ 
    322344    has_field('user_id', Integer, primary_key=True) 
    323345    has_field('user_name', Unicode(16), unique=True) 
     
    337359 
    338360class Permission(Entity): 
     361    """ 
     362    A relationship that determines what each Group can do 
     363    """ 
    339364    has_field('permission_id', Integer, primary_key=True) 
    340365    has_field('permission_name', Unicode(16), unique=True) 
  • branches/1.0/turbogears/startup.py

    r3366 r3617  
    225225 
    226226    if config.get("sqlalchemy.dburi"): 
    227         database.bind_meta_data() 
     227        database.get_engine() 
    228228 
    229229    # Start all TurboGears extensions 
  • branches/1.0/turbogears/tests/test_sqlalchemy.py

    r3512 r3617  
    55from sqlalchemy import * 
    66from sqlalchemy.orm import * 
    7 from sqlalchemy.ext.activemapper import ActiveMapper, column, one_to_many 
    87 
    98from turbogears import config, redirect, expose, database, errorhandling 
    109from turbogears.testutil import create_request, capture_log, print_log, \ 
    1110                                sqlalchemy_cleanup 
    12 from turbogears.database import session, metadata, bind_meta_data 
     11from turbogears.database import session, metadata, get_engine 
    1312from turbogears.controllers import RootController 
    1413 
     14from sqlalchemy.ext import activemapper 
     15activemapper.metadata, activemapper.objectstore = metadata, session 
     16from sqlalchemy.ext.activemapper import ActiveMapper, column, one_to_many 
     17 
    1518config.update({"sqlalchemy.dburi" : "sqlite:///:memory:"}) 
    1619 
    17 bind_meta_data() 
     20get_engine() 
    1821 
    1922metadata.bind.echo = True 
     
    132135    session.clear() # should be done automatically, but just in case 
    133136    assert Person.query().get(21) is None 
    134      
     137 
    135138# Check that if a controller redirects, transactions are committed 
    136139def test_user_redirect(): 
     
    158161    doerr = errorhandling.exception_handler(handerr)(doerr) 
    159162    doerr = expose()(doerr) 
    160      
     163 
    161164def test_exc_rollback(): 
    162165    cherrypy.root = RbRoot() 
     
    194197        session.query(Test).get(1).val = 'b' 
    195198        return dict() 
    196     test2 = expose()(test2)         
     199    test2 = expose()(test2) 
    197200    def test3(self): 
    198201        assert session.query(Test).get(1).val == 'b' 
     
    222225    thrdb.start() 
    223226    thrdb.join() 
    224      
     227 
    225228    create_request("/test3") 
    226229    print cherrypy.response.body[0] 
  • branches/1.0/turbogears/testutil.py

    r3407 r3617  
    258258    sqlalchemy.orm.clear_mappers() 
    259259    database.metadata.clear() 
    260     database.metadata.dispose() 
     260    try: # if ThreadLocalMetaData is used 
     261        database.metadata.dispose() 
     262    except AttributeError: 
     263        pass 
    261264 
    262265__all__ = ["create_request", "call", "DBTest", "createRequest", 
  • branches/1.0/turbogears/visit/savisit.py

    r3484 r3617  
    1 import turbogears 
    2 #from datetime import * 
    31from datetime import datetime 
     2 
    43from sqlalchemy import * 
    5 from sqlalchemy.orm import mapper, class_mapper 
     4from sqlalchemy.orm import class_mapper 
    65 
     6from turbogears import config 
    77from turbogears.visit.api import BaseVisitManager, Visit 
    8 from turbogears import config 
    9 from turbogears.database import bind_meta_data, metadata, session, get_engine 
     8from turbogears.database import get_engine, metadata, session, mapper 
    109from turbogears.util import load_class 
    1110 
     
    2928            log.error(msg) 
    3029 
    31         bind_meta_data() 
     30        get_engine() 
    3231        if visit_class is TG_Visit: 
    3332            mapper(visit_class, visits_table) 
     
    3736        Create the Visit table if it doesn't already exist 
    3837        ''' 
    39         class_mapper(visit_class).local_table.create( 
    40                 checkfirst=True) 
     38        get_engine() 
     39        class_mapper(visit_class).local_table.create(checkfirst=True) 
    4140 
    4241    def new_visit_with_key(self, visit_key): 
     
    4645        visit.created = created 
    4746        visit.expiry = created + self.timeout 
    48         session.save(visit) 
    4947        session.flush() 
    5048        return Visit(visit_key, True)