# HG changeset patch
# Parent 6b65e6ec7bc777a8635ec1a19162e39c81b7c877
db: introduce context managers for accessing `Connection` instances.

This continues the work started in #8751.

Part of #9636.

diff -r 6b65e6ec7bc7 trac/db/api.py
--- a/trac/db/api.py	Mon Sep 13 12:37:52 2010 +0200
+++ b/trac/db/api.py	Mon Sep 13 15:58:46 2010 +0200
@@ -30,6 +30,8 @@ _transaction_local = ThreadLocal(db=None
 def with_transaction(env, db=None):
     """Function decorator to emulate a context manager for database
     transactions.
+
+    :deprecated: use `env.db_{query|transaction}`
     
     >>> def api_method(p1, p2):
     >>>     result[0] = value1
@@ -90,6 +92,61 @@ def get_read_db(env):
     return _transaction_local.db or DatabaseManager(env).get_connection()
 
 
+class TransactionContextMgr(object):
+    """Transactioned Database Context Manager
+
+    The outermost such context manager will either commit or rollback,
+    depending on the context being exited normally or after an exception.
+    """
+    def __init__(self, env):
+        self._env = env
+
+    def __enter__(self):
+        db = _transaction_local.db
+        if not db:
+            _transaction_local.db = self._db = db = self._env.get_db_cnx()
+            self._env = None # we own the _transaction_local.db
+        return db
+
+    def __exit__(self, et, ev, tb): 
+        if self._env is None: 
+            _transaction_local.db = None
+            if et is None: 
+                self._db.commit()
+            else: 
+                self._db.rollback()
+
+class ReadOnlyConnection(object):
+    """Wrapper around connection objects suppressing commit/rollback methods.
+    
+    :since 0.13:
+    """
+    __slots__ = ('cnx', 'log')
+
+    def __init__(self, cnx, log=None):
+        self.cnx, self.log = cnx, log
+
+    def __getattr__(self, name):
+        if name in ('commit', 'rollback'):
+            raise AttributeError
+        return getattr(self.cnx, name)
+
+class QueryContextMgr(object):
+    """Database Context Manager for retrieving a Connection wrapper
+    with no commit or rollback"""
+    def __init__(self, env):
+        self._env = env
+
+    def __enter__(self):
+        db = _transaction_local.db
+        if not db:
+            db = self._env.get_db_cnx()
+        return ReadOnlyConnection(db)
+
+    def __exit__(self, et, ev, tb): 
+        pass
+
+
 class IDatabaseConnector(Interface):
     """Extension point interface for components that support the connection to
     relational databases."""
diff -r 6b65e6ec7bc7 trac/env.py
--- a/trac/env.py	Mon Sep 13 12:37:52 2010 +0200
+++ b/trac/env.py	Mon Sep 13 15:58:46 2010 +0200
@@ -25,7 +25,8 @@ from trac.cache import CacheManager
 from trac.config import *
 from trac.core import Component, ComponentManager, implements, Interface, \
                       ExtensionPoint, TracError
-from trac.db.api import DatabaseManager, get_read_db, with_transaction
+from trac.db.api import (DatabaseManager, QueryContextMgr, 
+                         TransactionContextMgr, get_read_db, with_transaction)
 from trac.util import copytree, create_file, get_pkginfo, makedirs
 from trac.util.compat import any
 from trac.util.concurrency import threading
@@ -326,9 +327,7 @@ class Environment(Component, ComponentMa
         return get_read_db(self)
 
     def with_transaction(self, db=None):
-        """Decorator for transaction functions.
-
-        See `trac.db.api.with_transaction` for detailed documentation."""
+        """Decorator for transaction functions (deprecated)"""
         return with_transaction(self, db)
 
     def get_read_db(self):
@@ -337,6 +336,14 @@ class Environment(Component, ComponentMa
         See `trac.db.api.get_read_db` for detailed documentation."""
         return get_read_db(self)
 
+    @property
+    def db_query(self):
+        return QueryContextMgr(self)
+
+    @property
+    def db_transaction(self):
+        return TransactionContextMgr(self)
+
     def shutdown(self, tid=None):
         """Close the environment."""
         RepositoryManager(self).shutdown(tid)

