Changeset 4132

Show
Ignore:
Timestamp:
02/21/08 15:21:21 (5 months ago)
Author:
chrisz
Message:

Complete fix for problems with SA 0.4.3, including ticket #1721, with unit-tests and some clean-up.

Files:

Legend:

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

    r4129 r4132  
    1 """Provides convenient access to an SQLObject or SQLAlchemy 
    2 managed database.""" 
     1"""Convenient access to an SQLObject or SQLAlchemy managed database.""" 
    32 
    43import sys 
     
    150149 
    151150        def _is_interesting_version(self): 
    152             "Return True only if version of MySQLdb <= 1.0.
     151            """Return True only if version of MySQLdb <= 1.0.""
    153152            import MySQLdb 
    154153            module_version = MySQLdb.version_info[0:2] 
     
    201200 
    202201        def begin(self, conn=None): 
    203             "Starts a transaction.
     202            """Start a transaction.""
    204203            if not self.supports_transactions: 
    205204                return conn 
     
    216215 
    217216        def commit(self): 
    218             "Commits the current transaction.
     217            """Commit the current transaction.""
    219218            if not self.supports_transactions: 
    220219                return 
     
    227226 
    228227        def rollback(self): 
    229             "Rolls back the current transaction.
     228            """Rollback the current transaction.""
    230229            if not self.supports_transactions: 
    231230                return 
     
    238237 
    239238        def end(self): 
    240             "Ends the transaction, returning to a standard connection.
     239            """End the transaction, returning to a standard connection.""
    241240            if not self.supports_transactions: 
    242241                return 
     
    323322 
    324323def commit_all(): 
    325     "Commits the Transactions in all registered hubs (for this thread)
     324    """Commits the transactions in all registered hubs (for this thread).""
    326325    for hub in hub_registry: 
    327326        hub.commit() 
    328327 
    329328def rollback_all(): 
    330     "Rolls back the Transactions in all registered hubs (for this thread)
     329    """Rollback the transactions in all registered hubs (for this thread).""
    331330    for hub in hub_registry: 
    332331        hub.rollback() 
    333332 
    334333def end_all(): 
    335     "Ends the Transactions in all registered hubs (for this thread)
     334    """End the transactions in all registered hubs (for this thread).""
    336335    for hub in hub_registry: 
    337336        hub.end() 
     
    349348    return _engine is not None 
    350349 
    351 [run_with_transaction.when("not _use_sa(args)")] # include "args" to avoid call being pre-cached 
     350# include "args" to avoid call being pre-cached 
     351[run_with_transaction.when("not _use_sa(args)")] 
    352352def so_rwt(func, *args, **kw): 
    353353    log.debug("Starting SQLObject transaction") 
     
    364364            raise 
    365365        except: 
    366             # No need to "rollback" the sqlalchemy unit of work, because nothing 
    367             # has hit the db yet. 
     366            # No need to "rollback" the sqlalchemy unit of work, 
     367            # because nothing has hit the db yet. 
    368368            rollback_all() 
    369369            raise 
     
    371371        end_all() 
    372372 
    373 [restart_transaction.when("not _use_sa(args)")] # include "args" to avoid call being pre-cached 
     373# include "args" to avoid call being pre-cached 
     374[restart_transaction.when("not _use_sa(args)")] 
    374375def so_restart_transaction(args): 
    375376    #log.debug("ReStarting SQLObject transaction") 
     
    388389        output = dispatch_error( 
    389390            controller, real_func, None, exception, *args, **kw) 
    390  
    391391    except dispatch.NoApplicableMethods: 
    392392        raise exc_type, exc_value, exc_trace 
    393  
    394393    else: 
    395394        del exc_trace 
     
    400399def sa_rwt(func, *args, **kw): 
    401400    log.debug("Starting SA transaction") 
    402     req = cherrypy.request 
    403     req.sa_transaction = make_sa_transaction(session) 
     401    request = cherrypy.request 
     402    request.sa_transaction = make_sa_transaction(session) 
    404403    try: 
    405404        try: 
    406405            retval = func(*args, **kw) 
    407406        except (cherrypy.HTTPRedirect, cherrypy.InternalRedirect): 
    408             log.debug('Redirect in transaction - will commit now') 
    409407            # If a redirect happens, commit and proceed with redirect 
    410             if sa_tr_active(req.sa_transaction): 
    411                 req.sa_transaction.commit() 
     408            if sa_transaction_active(request.sa_transaction): 
     409                log.debug('Redirect in active transaction - will commit now') 
     410                if hasattr(session, 'commit'): 
     411                    session.commit() 
     412                else: # SA < 0.4 
     413                    request.sa_transaction.commit() 
     414            else: 
     415                log.debug('Redirect in inactive transaction') 
    412416            raise 
    413417        except: 
    414             log.debug('Error in transaction - will rollback now') 
    415418            # If any other exception happens, rollback and re-raise error 
    416             if sa_tr_active(req.sa_transaction): 
    417                 req.sa_transaction.rollback() 
     419            if sa_transaction_active(request.sa_transaction): 
     420                log.debug('Error in active transaction - will rollback now') 
     421                if hasattr(session, 'rollback'): 
     422                    session.rollback() 
     423                else: # SA < 0.4 
     424                    request.sa_transaction.rollback() 
     425            else: 
     426                log.debug('Error in inactive transaction') 
    418427            raise 
    419428        # If the call was successful, commit and proceed 
    420         if sa_tr_active(req.sa_transaction): 
    421             log.debug('Successful transaction - will commit now') 
    422             req.sa_transaction.commit() 
     429        if sa_transaction_active(request.sa_transaction): 
     430            log.debug('Transaction is still active - will commit now') 
     431            if hasattr(session, 'commit'): 
     432                session.commit() 
     433            else: # SA < 0.4 
     434                request.sa_transaction.commit() 
     435        else: 
     436            log.debug('Transaction is already inactive') 
    423437    finally: 
    424438        log.debug('Ending SA transaction') 
    425         req.sa_transaction.close() 
     439        session.close() 
    426440    return retval 
    427441 
     
    430444def sa_restart_transaction(args): 
    431445    log.debug("Restarting SA transaction") 
    432     req = cherrypy.request 
    433     if sa_tr_active(req.sa_transaction): 
    434         req.sa_transaction.rollback() 
    435     session.clear() 
    436     req.sa_transaction = make_sa_transaction(session) 
    437  
    438 def sa_tr_active(tr): 
    439     if hasattr(session, 'context'): 
    440         # SA 0.3 
    441         return tr.session.transaction 
     446    request = cherrypy.request 
     447    if sa_transaction_active(request.sa_transaction): 
     448        log.debug('Transaction is still active - will rollback now') 
     449        if hasattr(session, 'rollback'): 
     450            session.rollback() 
     451        else: # SA < 0.4 
     452            request.sa_transaction.rollback() 
    442453    else: 
    443         # SA 0.4 can be treated as always active; if no transaction 
    444         # is active, commit and rollback quietly do nothing 
    445         return True 
     454        log.debug('Transaction is already inactive') 
     455    session.close() 
     456    request.sa_transaction = make_sa_transaction(session) 
    446457 
    447458def make_sa_transaction(session): 
    448     if hasattr(session, 'context'): 
    449         # SA 0.3 
    450         tr = session.create_transaction() 
    451         return tr 
    452     else: 
    453         # SA 0.4 
    454         session.begin() 
    455         return session 
     459    """Create a new transaction in an SA session.""" 
     460    try: 
     461        return session.begin() 
     462    except AttributeError: # SA < 0.4 
     463        return session.create_transaction() 
     464 
     465def sa_transaction_active(transaction): 
     466    """Check whether SA transaction is still active.""" 
     467    try: 
     468        return transaction and transaction.is_active 
     469    except AttributeError: # SA < 0.4.3 
     470        return transaction.session.transaction 
    456471 
    457472def so_to_dict(sqlobj): 
    458     "Converts SQLObject to a dictionary based on columns
     473    """Convert SQLObject to a dictionary based on columns.""
    459474    d = {} 
    460475    if sqlobj == None: 
     
    471486 
    472487def so_columns(sqlclass, columns=None): 
    473     """Returns a dict with all columns from a SQLObject including those from 
    474     InheritableSO's bases""" 
     488    """Return a dict with all columns from a SQLObject. 
     489 
     490    This includes the columns from InheritableSO's bases. 
     491 
     492    """ 
    475493    if columns is None: 
    476494        columns = {} 
     
    482500 
    483501def so_joins(sqlclass, joins=None): 
    484     """Returns a list with all joins from a SQLObject including those from 
    485     InheritableSO's bases""" 
     502    """Return a list with all joins from a SQLObject. 
     503 
     504    The list includes the columns from InheritableSO's bases. 
     505 
     506    """ 
    486507    if joins is None: 
    487508        joins = [] 
  • branches/1.0/turbogears/tests/test_paginate.py

    r4122 r4132  
    1 "Tests for paginate
     1"""Tests for paginate""
    22 
    33import unittest 
  • branches/1.0/turbogears/tests/test_sqlalchemy.py

    r4122 r4132  
    1 "Tests for SQLAlchemy support
     1"""Tests for SQLAlchemy support""
    22 
    33import cherrypy, os, threading, turbogears 
    44 
    5 from sqlalchemy import * 
     5from sqlalchemy import MetaData, Table, Column, ForeignKey, Integer, String 
    66from sqlalchemy.ext.activemapper import ActiveMapper, column, one_to_many 
    77 
    8 from turbogears import config, redirect, expose, database, errorhandling 
    9 from turbogears.testutil import create_request, capture_log, print_log, \ 
    10                                 sqlalchemy_cleanup 
     8from turbogears import config, redirect, expose, errorhandling 
    119from turbogears.database import get_engine, metadata, session, mapper 
    1210from turbogears.controllers import RootController 
    13  
     11from turbogears.testutil import create_request, sqlalchemy_cleanup, \ 
     12    capture_log, print_log 
     13 
     14 
     15# Fixture 
    1416 
    1517class User(object): 
     
    6870def teardown_module(): 
    6971    metadata.drop_all() 
    70     sqlalchemy_cleanup() 
    7172    fresh_metadata.drop_all() 
    7273    fresh_metadata.bind.dispose() 
    7374    if os.path.exists('freshtest.db'): 
    7475        os.unlink('freshtest.db') 
    75  
     76    sqlalchemy_cleanup() 
     77 
     78 
     79# Simple database tests 
    7680 
    7781def test_query_in_session(): 
     
    102106    assert len(ford.addresses) == 1 
    103107 
     108 
     109# Exception handling 
     110 
    104111class MyRoot(RootController): 
     112    """A small root controller for our exception handling tests""" 
     113 
    105114    def no_error(self, name): 
    106         p = Person(name=name) 
     115        """Test controller""" 
     116        Person(name=name) 
    107117        raise redirect("/confirm") 
    108118    no_error = expose()(no_error) 
    109119 
    110120    def e_handler(self, tg_exceptions=None): 
     121        """Test error handler""" 
    111122        cherrypy.response.code = 501 
    112         return "An exception ocurred: %r (%s)" % ((tg_exceptions,)*2) 
    113  
    114     def create_person(self, id, docom=0, doerr=0): 
    115         p = Person(id=id) 
     123        return "An exception occurred: %r (%s)" % ((tg_exceptions,)*2) 
     124 
     125    def create_person(self, id, docom=0, doerr=0, doflush=0): 
     126        """Test controller""" 
     127        Person(id=id) 
    116128        if int(docom): 
    117129            cherrypy.request.sa_transaction.commit() 
     
    120132        if int(doerr) == 2: 
    121133            raise turbogears.redirect('/') 
    122         return "No exceptions ocurred" 
     134        if int(doflush): 
     135            try: 
     136                session.flush() 
     137            except Exception: 
     138                if int(doflush) == 1: 
     139                    raise 
     140        return "No exceptions occurred" 
    123141    create_person = expose()(create_person) 
    124142    create_person = errorhandling.exception_handler(e_handler)(create_person) 
    125143 
     144 
    126145def test_implicit_trans_no_error(): 
     146    """If a controller runs sucessfully, the transaction is commited.""" 
    127147    capture_log("turbogears.database") 
    128148    cherrypy.root = MyRoot() 
     
    134154 
    135155def test_raise_sa_exception(): 
     156    """If a controller causes an SA exception, it is raised properly.""" 
    136157    capture_log("turbogears.database") 
    137158    cherrypy.root = MyRoot() 
    138159    create_request("/create_person?id=20") 
    139160    output = cherrypy.response.body[0] 
    140     print output 
    141     assert "No exceptions" in output 
    142  
     161    assert 'No exceptions occurred' in output 
    143162    create_request("/create_person?id=20") 
    144163    output = cherrypy.response.body[0] 
    145     print output 
    146  
    147     # Note that the specific DB2API may be either OperationalError or 
    148     # IntegrityError depending on what version of sqlite and pysqlite 
    149     # is used. 
    150     # SA 0.3 uses SQLError; 0.4 DBAPIError 
    151     assert "SQLError" in output or "DBAPIError" in output 
    152  
    153 # Check that if a controller raises an exception, transactions are rolled back 
     164    # SA 0.3 uses SQLError, 0.4 DBAPIError 
     165    assert 'SQLError' in output or 'DBAPIError' in output 
     166 
    154167def test_user_exception(): 
     168    """If a controller raises an exception, transactions are rolled back.""" 
    155169    cherrypy.root = MyRoot() 
    156170    create_request("/create_person?id=21&doerr=1") 
     
    158172    assert Person.query().get(21) is None 
    159173 
    160 # Check that if a controller redirects, transactions are committed 
    161174def test_user_redirect(): 
     175    """If a controller redirects, transactions are committed.""" 
    162176    cherrypy.root = MyRoot() 
    163177    create_request("/create_person?id=22&doerr=2") 
     
    165179    assert Person.query().get(22) is not None 
    166180 
    167 # Check it's safe to commit a transaction in the controller 
    168181def test_cntrl_commit(): 
     182    """It's safe to commit a transaction in the controller.""" 
    169183    cherrypy.root = MyRoot() 
    170184    create_request("/create_person?id=23&docom=1") 
    171     assert 'InvalidRequestError: This transaction is inactive' not in cherrypy.response.body[0] 
    172  
    173 # Check an exception within a tg.exception_handler causes a rollback 
     185    assert 'InvalidRequestError' not in cherrypy.response.body[0] 
     186 
     187def test_cntrl_flush(): 
     188    """It's safe to flush in the controller.""" 
     189    cherrypy.root = MyRoot() 
     190    create_request("/create_person?id=24&doflush=1") 
     191    assert 'No exceptions occurred' in cherrypy.response.body[0] 
     192    create_request("/create_person?id=24&doflush=0") 
     193    assert 'IntegrityError' in cherrypy.response.body[0] 
     194    create_request("/create_person?id=24&doflush=1") 
     195    assert 'IntegrityError' in cherrypy.response.body[0] 
     196    create_request("/create_person?id=24&doflush=2") 
     197    assert 'No exceptions occurred' in cherrypy.response.body[0] 
     198 
     199 
     200# Exception handling with rollback 
     201 
    174202class RbRoot(RootController): 
     203    """A small root controller for our transaction rollback tests""" 
     204 
    175205    def handerr(self, id): 
     206        """Test error handler""" 
    176207        Person(id=int(id)+1) 
    177208        return dict() 
     209 
    178210    def doerr(self, id, dorb=0): 
     211        """Test controller""" 
    179212        Person(id=id) 
    180213        if int(dorb): 
     
    184217    doerr = expose()(doerr) 
    185218 
     219 
    186220def test_exc_rollback(): 
     221    """"An exception within a controller method causes a rollback.""" 
    187222    cherrypy.root = RbRoot() 
    188     create_request('/doerr?id=24') 
    189     print cherrypy.response.body[0] 
    190     assert Person.query().get(24) is None 
    191     assert Person.query().get(25) is not None 
    192  
    193 # Check that if controller method manually rollbacks, error handler doesn't cause problems 
     223    create_request('/doerr?id=25') 
     224    assert Person.query().get(25) is None 
     225    assert Person.query().get(26) is not None 
     226 
    194227def test_exc_done_rollback(): 
     228    """No problems with error handler if controller manually rollbacks.""" 
    195229    cherrypy.root = RbRoot() 
    196     create_request('/doerr?id=26&dorb=1') 
    197     print cherrypy.response.body[0] 
     230    create_request('/doerr?id=27&dorb=1') 
    198231    assert cherrypy.response.body[0] == '{"tg_flash": null}' 
    199232 
    200 #-- 
    201 # Check for session freshness, ticket #1419 
    202 # It checks that changes made to the data in thread B are reflected in thread A. 
    203 #-- 
     233 
     234# Session freshness tests 
    204235 
    205236class FreshRoot(RootController): 
     237    """A small root controller for our session freshness tests""" 
     238 
    206239    def test1(self): 
    207240        assert session.query(Test).get(1).val == 'a' 
    208241        return dict() 
    209242    test1 = expose()(test1) 
     243 
    210244    def test2(self): 
    211245        session.query(Test).get(1).val = 'b' 
    212246        return dict() 
    213247    test2 = expose()(test2) 
     248 
    214249    def test3(self): 
    215250        assert session.query(Test).get(1).val == 'b' 
     
    217252    test3 = expose()(test3) 
    218253 
     254 
    219255def test_session_freshness(): 
     256    """Check for session freshness. 
     257 
     258    Changes made to the data in thread B should be reflected in thread A. 
     259 
     260    """ 
    220261    fresh_metadata.bind.execute(test_table.insert(), dict(id=1, val='a')) 
    221  
    222262    cherrypy.root = FreshRoot() 
    223  
    224263    create_request("/test1") 
    225     print cherrypy.response.body[0] 
    226264    assert 'AssertionError' not in cherrypy.response.body[0] 
    227  
    228265    # Call test2 in a different thread 
    229266    class ThreadB(threading.Thread): 
    230267        def run(self): 
    231268            create_request("/test2") 
    232             print cherrypy.response.body[0] 
    233269            assert 'AssertionError' not in cherrypy.response.body[0] 
    234270    thrdb = ThreadB() 
    235271    thrdb.start() 
    236272    thrdb.join() 
    237  
    238273    create_request("/test3") 
    239     print cherrypy.response.body[0] 
    240274    assert 'AssertionError' not in cherrypy.response.body[0] 
  • branches/1.0/turbogears/testutil.py

    r3967 r4132  
     1import os 
    12import types 
    2 import inspect 
    33import logging 
    44import unittest 
     5import Cookie 
    56import cStringIO as StringIO 
    6 import Cookie 
    77 
    88import cherrypy 
     9from cherrypy import _cphttptools 
     10 
    911try: 
    1012    import sqlobject 
     
    1214except ImportError: 
    1315    sqlobject = None 
    14  
    1516try: 
    1617    import sqlalchemy 
     
    1819    sqlalchemy = None 
    1920 
    20 from cherrypy import _cphttptools 
    21  
    22 from turbogears import database, controllers, startup, validators, config, \ 
    23                        update_config 
     21from turbogears import startup, config, update_config, \ 
     22    controllers, database, validators 
     23from turbogears.identity import current_provider 
    2424from turbogears.util import get_model 
    25  
    26 import os 
    27 from os.path import * 
    2825 
    2926cwd = os.getcwd() 
     
    3431        for f in w[2]: 
    3532            if f.endswith('.kid'): 
    36                 f = join(w[0], f[:-3] + 'pyc') 
    37                 if exists(f): 
     33                f = os.path.join(w[0], f[:-3] + 'pyc') 
     34                if os.path.exists(f): 
    3835                    os.remove(f) 
    3936 
    4037# Override config of all applications with test.cfg 
    41 if exists(join(cwd, "test.cfg")): 
     38if os.path.exists(os.path.join(cwd, "test.cfg")): 
    4239    modulename = None 
    4340    for w in os.walk(cwd): 
     
    4643            modulename = "%s.app" % config_dir.replace(os.sep, ".") 
    4744            break 
    48     update_config(configfile=join(cwd, "test.cfg"), modulename=modulename) 
     45    update_config(configfile=os.path.join(cwd, "test.cfg"), 
     46        modulename=modulename) 
    4947else: 
    5048    database.set_db_uri("sqlite:///:memory:") 
    5149 
    52 config.update({"global" : {"tg.new_style_logging" : True}}) 
    53 config.update({"global" : {"autoreload.on" : False}}) 
     50config.update({'global': 
     51        {'autoreload.on': False, 'tg.new_style_logging': True}}) 
     52 
    5453 
    5554def start_cp(): 
     
    5857        config.update({"cherrypy_started" : True}) 
    5958 
     59 
    6060test_user = None 
    6161 
     62 
    6263def set_identity_user(user): 
    63     "Setup a user which will be used to configure request's identity.
     64    """Setup a user for configuring request's identity.""
    6465    global test_user 
    6566    test_user = user 
    6667 
     68 
    6769def attach_identity(req): 
    68     from turbogears.identity import current_provider 
    6970    if config.get("identity.on", False): 
    70         if test_user: 
    71             id = current_provider.authenticated_identity(test_user) 
    72         else: 
    73             id = current_provider.anonymous_identity() 
    74         req.identity = id 
     71        req.identity = (test_user 
     72            and current_provider.authenticated_identity(test_user) 
     73            or current_provider.anonymous_identity()) 
     74 
    7575 
    7676def create_request(request, method="GET", protocol="HTTP/1.1", 
    77     headers={}, rfile=None, clientAddress="127.0.0.1", 
    78     remoteHost="localhost", scheme="http"): 
     77        headers={}, rfile=None, clientAddress="127.0.0.1", 
     78        remoteHost="localhost", scheme="http"): 
    7979    start_cp() 
    8080    if not rfile: 
     
    9494    req.run(" ".join((method, request, protocol)), headerList, rfile) 
    9595 
    96 createRequest = create_request 
     96createRequest = create_request # deprecated 
    9797 
    9898 
    9999class BrowsingSession(object): 
     100 
    100101    def __init__(self): 
    101102        self.visit = None 
     
    117118    return output 
    118119 
     120 
    119121class DummySession: 
    120122    session_storage = dict 
    121123    to_be_loaded = None 
    122124 
     125 
    123126class DummyRequest: 
    124     "A very simple dummy request." 
     127    """A very simple dummy request.""" 
     128 
    125129    remote_host = "127.0.0.1" 
    126130 
     
    131135        self.base = '' 
    132136        self._session = DummySession() 
     137 
    133138    def purge__(self): 
    134139        pass 
     140 
    135141 
    136142def call(method, *args, **kw): 
     
    139145    return output 
    140146 
     147 
    141148def call_with_request(method, request, *args, **kw): 
    142     "More fine-grained version of call method, allowing to use request/response." 
     149    """More fine-grained version of call method. 
     150 
     151    This allows using request/response. 
     152 
     153    """ 
    143154    orig_proc_output = controllers._process_output 
    144155    controllers._process_output = _return_directly 
     
    158169 
    159170class DBTest(unittest.TestCase): 
     171 
    160172    model = None 
    161173 
     
    187199                item.dropTable(ifExists=True) 
    188200 
     201 
    189202def reset_cp(): 
    190203    cherrypy.root = None 
    191204 
     205 
    192206def catch_validation_errors(widget, value): 
    193     """ Catches and unpacks validation errors. For testing purposes. """ 
    194     errors = {} 
     207    """Catch and unpack validation errors (for testing purposes).""" 
    195208    try: 
    196209        value = widget.validate(value) 
    197     except validators.Invalid, e: 
    198         if hasattr(e, 'unpack_errors'): 
    199             errors = e.unpack_errors() 
    200         else: 
    201             errors = e 
     210    except validators.Invalid, errors: 
     211        try: 
     212            errors = errors.unpack_errors() 
     213        except AttributeError: 
     214            pass 
     215    else: 
     216        errors = {} 
    202217    return value, errors 
     218 
    203219 
    204220class MemoryListHandler(logging.Handler): 
     
    223239 
    224240_memhandler = MemoryListHandler() 
     241 
     242 
    225243_currentcat = None 
    226244 
     245 
    227246def capture_log(category): 
    228     """Category can either be a single category (a string like 'foo.bar') 
    229     or a list of them. You <em>must</em> call print_log() to reset when 
    230     you're done.""" 
     247    """Capture log for one category. 
     248 
     249    The category can either be a single category (a string like 'foo.bar') 
     250    or a list of them. You *must* call print_log() to reset when you're done. 
     251 
     252    """ 
    231253    global _currentcat 
    232254    assert not _currentcat 
     
    239261        log.addHandler(_memhandler) 
    240262 
     263 
    241264def _reset_logging(): 
    242     """Manages the resetting of the loggers""" 
     265    """Manage the resetting of the loggers.""" 
    243266    global _currentcat 
    244267    if not _currentcat: 
     
    249272    _currentcat = None 
    250273 
     274 
    251275def print_log(): 
    252     """Prints the log captured by capture_log to stdout, resets that log 
    253     and resets the temporarily added handlers.""" 
     276    """Print the log captured by capture_log to stdout. 
     277 
     278    Resets that log and resets the temporarily added handlers. 
     279 
     280    """ 
    254281    _reset_logging() 
    255282    _memhandler.print_log() 
    256283 
     284 
    257285def get_log(): 
    258     """Returns the list of log messages captured by capture_log, 
    259     resets that log and resets the temporarily added handlers.""" 
     286    """Return the list of log messages captured by capture_log. 
     287 
     288    Resets that log and resets the temporarily added handlers. 
     289 
     290    """ 
    260291    _reset_logging() 
    261292    return _memhandler.get_log() 
    262293 
     294 
    263295def sqlalchemy_cleanup(): 
     296    database.metadata.clear() 
     297    try: 
     298        database.metadata.dispose() 
     299    except AttributeError: # not threadlocal 
     300        if database.metadata.bind: 
     301            database.metadata.bind.dispose() 
    264302    database._engine = None 
    265     sqlalchemy.orm.clear_mappers() 
    266     database.metadata.clear() 
    267     try: # if ThreadLocalMetaData is used 
    268         database.metadata.dispose() 
    269     except AttributeError: 
    270         pass 
     303    if database.mapper == database.session.mapper: 
     304        # the following does not work for SA < 0.4 
     305        sqlalchemy.orm.clear_mappers() 
     306 
    271307 
    272308__all__ = ["create_request", "call", "DBTest", "createRequest", 
  • branches/1.1/turbogears/database.py

    r4129 r4132  
    1 """Provides convenient access to an SQLObject or SQLAlchemy 
    2 managed database.""" 
     1"""Convenient access to an SQLObject or SQLAlchemy managed database.""" 
    32 
    43import sys 
     
    176175 
    177176        def _is_interesting_version(self): 
    178             "Return True only if version of MySQLdb <= 1.0.
     177            """Return True only if version of MySQLdb <= 1.0.""
    179178            import MySQLdb 
    180179            module_version = MySQLdb.version_info[0:2] 
     
    227226 
    228227        def begin(self, conn=None): 
    229             "Starts a transaction.
     228            """Start a transaction.""
    230229            if not self.supports_transactions: 
    231230                return conn 
     
    242241 
    243242        def commit(self): 
    244             "Commits the current transaction.
     243            """Commit the current transaction.""
    245244            if not self.supports_transactions: 
    246245                return 
     
    253252 
    254253        def rollback(self): 
    255             "Rolls back the current transaction.
     254            """Rollback the current transaction.""
    256255            if not self.supports_transactions: 
    257256                return 
     
    264263 
    265264        def end(self): 
    266             "Ends the transaction, returning to a standard connection.
     265            """End the transaction, returning to a standard connection.""
    267266            if not self.supports_transactions: 
    268267                return 
     
    349348 
    350349def commit_all(): 
    351     "Commits the Transactions in all registered hubs (for this thread)
     350    """Commit the transactions in all registered hubs (for this thread).""
    352351    for hub in hub_registry: 
    353352        hub.commit() 
    354353 
    355354def rollback_all(): 
    356     "Rolls back the Transactions in all registered hubs (for this thread)
     355    """Rollback the transactions in all registered hubs (for this thread).""
    357356    for hub in hub_registry: 
    358357        hub.rollback() 
    359358 
    360359def end_all(): 
    361     "Ends the Transactions in all registered hubs (for this thread)
     360    """End the transactions in all registered hubs (for this thread).""
    362361    for hub in hub_registry: 
    363362        hub.end() 
     
    390389            raise 
    391390        except: 
    392             # No need to "rollback" the sqlalchemy unit of work, because nothing 
    393             # has hit the db yet. 
     391            # No need to "rollback" the sqlalchemy unit of work, 
     392            # because nothing has hit the db yet. 
    394393            rollback_all() 
    395394            raise 
     
    425424def sa_rwt(func, *args, **kw): 
    426425    log.debug("Starting SA transaction") 
    427     session.begin() 
     426    request = cherrypy.request 
     427    request.sa_transaction = session.begin() 
    428428    try: 
    429         cherrypy.request.sa_transaction = session # for compatibility 
    430429        try: 
    431430            retval = func(*args, **kw) 
    432431        except (cherrypy.HTTPRedirect, cherrypy.InternalRedirect): 
    433             log.debug('Redirect in transaction - will commit now') 
    434432            # If a redirect happens, commit and proceed with redirect. 
    435             session.commit() 
     433            if sa_transaction_active(request.sa_transaction): 
     434                log.debug('Redirect in active transaction - will commit now') 
     435                session.commit() 
     436            else: 
     437                log.debug('Redirect in inactive transaction') 
    436438            raise 
    437439        except: 
    438             log.debug('Error in transaction - will rollback now') 
    439440            # If any other exception happens, rollback and re-raise error 
    440             session.rollback() 
     441            if sa_transaction_active(request.sa_transaction): 
     442                log.debug('Error in active transaction - will rollback now') 
     443                session.rollback() 
     444            else: 
     445                log.debug('Error in inactive transaction') 
    441446            raise 
    442447        # If the call was successful, commit and proceed 
    443         log.debug('Successful transaction - will commit now') 
    444         session.commit() 
     448        if sa_transaction_active(request.sa_transaction): 
     449            log.debug('Transaction is still active - will commit now') 
     450            session.commit() 
     451        else: 
     452            log.debug('Transaction is already inactive') 
    445453    finally: 
    446454        log.debug('Ending SA transaction') 
     
    452460def sa_restart_transaction(args): 
    453461    log.debug("Restarting SA transaction") 
    454     session.rollback() 
    455     session.clear() 
    456     session.begin() 
     462    request = cherrypy.request 
     463    if sa_transaction_active(request.sa_transaction): 
     464        log.debug('Transaction is still active - will rollback now') 
     465        session.rollback() 
     466    else: 
     467        log.debug('Transaction is already inactive') 
     468    session.close() 
     469    request.sa_transaction = session.begin() 
     470 
     471def sa_transaction_active(transaction): 
     472    """Check whether SA transaction is still active.""" 
     473    try: 
     474        return transaction and transaction.is_active 
     475    except AttributeError: # SA < 0.4.3 
     476        return transaction.session.transaction 
    457477 
    458478def so_to_dict(sqlobj): 
    459     "Converts SQLObject to a dictionary based on columns
     479    """Convert SQLObject to a dictionary based on columns.""
    460480    d = {} 
    461481    if sqlobj == None: 
     
    472492 
    473493def so_columns(sqlclass, columns=None): 
    474     """Returns a dict with all columns from a SQLObject including those from 
    475     InheritableSO's bases""" 
     494    """Return a dict with all columns from a SQLObject. 
     495 
     496    This includes the columns from InheritableSO's bases. 
     497 
     498    """ 
    476499    if columns is None: 
    477500        columns = {} 
     
    483506 
    484507def so_joins(sqlclass, joins=None): 
    485     """Returns a list with all joins from a SQLObject including those from 
    486     InheritableSO's bases""" 
     508    """Return a list with all joins from a SQLObject. 
     509 
     510    The list includes the columns from InheritableSO's bases. 
     511 
     512    """ 
    487513    if joins is None: 
    488514        joins = [] 
  • branches/1.1/turbogears/tests/test_paginate.py

    r4122 r4132  
    1 "Tests for paginate
     1"""Tests for paginate""
    22 
    33import unittest 
  • branches/1.1/turbogears/tests/test_sqlalchemy.py

    r4122 r4132  
    1 "Tests for SQLAlchemy support
     1"""Tests for SQLAlchemy support""
    22 
    33import cherrypy, os, threading, turbogears 
     
    66from sqlalchemy.orm import relation 
    77 
    8 from turbogears import config, redirect, expose, database, errorhandling 
    9 from turbogears.testutil import capture_log, print_log, \ 
    10     create_request, sqlalchemy_cleanup 
     8from turbogears import config, redirect, expose, errorhandling 
    119from turbogears.database import metadata, session, mapper, \ 
    1210    bind_metadata, get_metadata 
    1311from turbogears.controllers import RootController 
    14  
     12from turbogears.testutil import create_request, sqlalchemy_cleanup, \ 
     13    capture_log, print_log 
     14 
     15 
     16# Fixture 
    1517 
    1618class User(object): 
     
    9193 
    9294 
     95# Database tests 
     96 
    9397def test_query_in_session(): 
    9498    i = users_table.insert() 
     
    114118 
    115119 
    116 # ------------ Exception Handling --------------- Start. 
     120# Exception handling 
    117121 
    118122class MyRoot(RootController): 
     123    """A small root controller for our exception handling tests""" 
     124 
     125    @expose() 
    119126    def no_error(self, name): 
    120         p = Person() 
    121         p.name = name 
    122         session.save(p) 
     127        """Test controller""" 
     128        Person(name=name) 
    123129        raise redirect("/someconfirmhandler") 
    124     no_error = expose()(no_error) 
    125130 
    126131    def e_handler(self, id, tg_exceptions=None): 
    127         """ 
    128         this handler is called KARL25 and all returns from it 
     132        """Test error handler 
     133 
     134        This handler is called KARL25 and all returns from it 
    129135        will be marked with this name :) 
     136 
    130137        """ 
    131138        cherrypy.response.code = 501 
    132139        msg = "KARL25 responding\n" 
    133140        msg += "user with id: '%s' should not be saved.\n" % id 
    134         msg += "An exception ocurred: %r (%s)" % ((tg_exceptions,)*2) 
     141        msg += "An exception occurred: %r (%s)" % ((tg_exceptions,)*2) 
    135142        return dict(msg=msg) 
    136143 
    137     def create_person(self, id, docom=0, doerr=0, name="John Doe"): 
    138         p = Person(
    139         p.id = id 
    140         p.name = name 
    141         session.save(p) 
    142  
     144    @expose() 
     145    @errorhandling.exception_handler(e_handler
     146    def create_person(self, id, 
     147