]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- sessionmaker module is out, replaced with simple function in session.py
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 2 Aug 2007 05:42:49 +0000 (05:42 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 2 Aug 2007 05:42:49 +0000 (05:42 +0000)
- scoping/class instrumenting behavior of sessionmaker moved into new scoping module
which implements scoped_session() (subject to potential name change)
- SessionContext / assignmapper are deprecated, replaced with scoped_session()

doc/build/content/datamapping.txt
lib/sqlalchemy/ext/assignmapper.py
lib/sqlalchemy/ext/sessioncontext.py
lib/sqlalchemy/orm/__init__.py
lib/sqlalchemy/orm/scoping.py [new file with mode: 0644]
lib/sqlalchemy/orm/session.py
lib/sqlalchemy/orm/sessionmaker.py [deleted file]
test/orm/unitofwork.py

index 6308c8d7be62ca415fd75fdeac7788e8a5380e1f..37afa0fd56b142e32b38dd5760e64a97619c9afa 100644 (file)
@@ -108,6 +108,12 @@ We're now ready to start talking to the database.  The ORM's "handle" to the dat
     >>> from sqlalchemy.orm import sessionmaker
     >>> Session = sessionmaker(bind=engine, autoflush=True, transactional=True)
 
+If you don't have an `Engine` yet, but want to define `Session`, define it without `bind`, and set the `bind` parameter later:
+
+    {python}
+    >>> Session = sessionmaker(autoflush=True, transactional=True)
+    >>> Session.configure(bind=engine)  # once engine is available
+    
 This `Session` class will create new `Session` objects which are bound to our database and have some various transactional characteristics.  Whenever you need to have a conversation with the database, you instantiate a `Session`:
 
     {python}
index cab5a9eae662c27f4fcfecb53f998e661af5e614..730b5313ba6a19f841da204549d904bba5f775ed 100644 (file)
@@ -26,6 +26,7 @@ def _monkeypatch_session_method(name, ctx, class_):
         setattr(class_, name, do)
         
 def assign_mapper(ctx, class_, *args, **kwargs):
+    util.warn_deprecated("assign_mapper is deprecated. Use scoped_session() instead.")
     extension = kwargs.pop('extension', None)
     if extension is not None:
         extension = util.to_list(extension)
index 8221bc4952c6f51ddbaa5bc88eeaa779c6dfead3..5ac8acb405e0a1035f837ee578296ce18d5cde3a 100644 (file)
@@ -1,35 +1,24 @@
-from sqlalchemy.util import ScopedRegistry, warn_deprecated
-from sqlalchemy.orm import create_session, object_session, MapperExtension, EXT_CONTINUE
+from sqlalchemy.orm.scoping import ScopedSession, _ScopedExt
+from sqlalchemy.util import warn_deprecated
+from sqlalchemy.orm import create_session
 
 __all__ = ['SessionContext', 'SessionContextExt']
 
-class SessionContext(object):
-    """A simple wrapper for ``ScopedRegistry`` that provides a
-    `current` property which can be used to get, set, or remove the
-    session in the current scope.
 
-    By default this object provides thread-local scoping, which is the
-    default scope provided by sqlalchemy.util.ScopedRegistry.
+class SessionContext(ScopedSession):
+    """Provides thread-local management of Sessions.
 
     Usage::
 
-      engine = create_engine(...)
-      def session_factory():
-          return Session(bind=engine)
-      context = SessionContext(session_factory)
+      context = SessionContext(sessionmaker(autoflush=True))
 
-      s = context.current # get thread-local session
-      context.current = Session(bind=other_engine) # set current session
-      del context.current # discard the thread-local session (a new one will
-                          # be created on the next call to context.current)
     """
 
     def __init__(self, session_factory=None, scopefunc=None):
-        warn_deprecated("SessionContext is deprecated.  Use Session=sessionmaker(scope='thread').")        
+        warn_deprecated("SessionContext is deprecated.  Use scoped_session().")
         if session_factory is None:
-            session_factory = create_session
-        self.registry = ScopedRegistry(session_factory, scopefunc)
-        super(SessionContext, self).__init__()
+            session_factory=create_session
+        super(SessionContext, self).__init__(session_factory, scopefunc=scopefunc)
 
     def get_current(self):
         return self.registry()
@@ -51,33 +40,11 @@ class SessionContext(object):
             return ext
 
     mapper_extension = property(_get_mapper_extension,
-                                doc="""Get a mapper extension that implements `get_session` using this context.""")
-
-
-class SessionContextExt(MapperExtension):
-    """A mapper extension that provides sessions to a mapper using ``SessionContext``."""
-
-    def __init__(self, context):
-        MapperExtension.__init__(self)
-        self.context = context
-
-    def get_session(self):
-        return self.context.current
-
-    def init_instance(self, mapper, class_, oldinit, instance, args, kwargs):
-        session = kwargs.pop('_sa_session', self.context.current)
-        session._save_impl(instance, entity_name=kwargs.pop('_sa_entity_name', None))
-        return EXT_CONTINUE
-
-    def init_failed(self, mapper, class_, instance, args, kwargs):
-        object_session(instance).expunge(instance)
-        return EXT_CONTINUE
-        
-    def dispose_class(self, mapper, class_):
-        if hasattr(class_, '__init__') and hasattr(class_.__init__, '_oldinit'):
-            if class_.__init__._oldinit is not None:
-                class_.__init__ = class_.__init__._oldinit
-            else:
-                delattr(class_, '__init__')
-                
-            
+                                doc="""Get a mapper extension that implements `get_session` using this context.  Deprecated.""")
+
+
+class SessionContextExt(_ScopedExt):
+    def __init__(self, *args, **kwargs):
+        warn_deprecated("SessionContextExt is deprecated.  Use ScopedSession(enhance_classes=True)")
+        super(SessionContextExt, self).__init__(*args, **kwargs)
+
index 26b583bb1b3eee4990c069f6c0bf890074e075de..646135e8ca128e48d482129e53ce86e882a9a3b5 100644 (file)
@@ -21,25 +21,25 @@ from sqlalchemy.orm.query import Query
 from sqlalchemy.orm.util import polymorphic_union
 from sqlalchemy.orm.session import Session as _Session
 from sqlalchemy.orm.session import object_session, attribute_manager, sessionmaker
-from sqlalchemy.orm.sessionmaker import sessionmaker
+from sqlalchemy.orm.scoping import ScopedSession as scoped_session
 
 __all__ = [ 'relation', 'column_property', 'composite', 'backref', 'eagerload',
             'eagerload_all', 'lazyload', 'noload', 'deferred', 'defer',
             'undefer', 'undefer_group', 'extension', 'mapper', 'clear_mappers',
             'compile_mappers', 'class_mapper', 'object_mapper', 'sessionmaker',
-            'dynamic_loader', 'MapperExtension', 'Query', 'polymorphic_union',
+            'scoped_session', 'dynamic_loader', 'MapperExtension', 'Query', 'polymorphic_union',
             'create_session', 'synonym', 'contains_alias',
             'contains_eager', 'EXT_CONTINUE', 'EXT_STOP', 'EXT_PASS',
             'object_session', 'PropComparator' ]
 
-
 def create_session(bind=None, **kwargs):
-    """create a new version 0.3-style [sqlalchemy.orm.session#Session].
+    """create a new [sqlalchemy.orm.session#Session].
     
     The session by default does not begin a transaction, and requires that
     flush() be called explicitly in order to persist results to the database.
+    
+    It is recommended to use the sessionmaker() function instead of create_session().
     """
-    sautil.warn_deprecated("create_session() is deprecated.  Use Session=sessionmaker() instead.")
     kwargs.setdefault('autoflush', False)
     kwargs.setdefault('transactional', False)
     return _Session(bind=bind, **kwargs)
diff --git a/lib/sqlalchemy/orm/scoping.py b/lib/sqlalchemy/orm/scoping.py
new file mode 100644 (file)
index 0000000..3ae63b4
--- /dev/null
@@ -0,0 +1,111 @@
+from sqlalchemy.util import ScopedRegistry, warn_deprecated
+from sqlalchemy.orm import MapperExtension, EXT_CONTINUE
+from sqlalchemy.orm.session import Session
+from sqlalchemy.orm.mapper import global_extensions
+from sqlalchemy import exceptions
+import types
+
+__all__ = ['ScopedSession']
+
+
+class ScopedSession(object):
+    """Provides thread-local management of Sessions.
+
+    Usage::
+
+      Session = scoped_session(sessionmaker(autoflush=True), enhance_classes=True)
+
+    """
+
+    def __init__(self, session_factory, scopefunc=None, enhance_classes=False):
+        self.session_factory = session_factory
+        self.enhance_classes = enhance_classes
+        self.registry = ScopedRegistry(session_factory, scopefunc)
+        if self.enhance_classes:
+            global_extensions.append(_ScopedExt(self))
+
+    def __call__(self, **kwargs):
+        if len(kwargs):
+            scope = kwargs.pop('scope', False)
+            if scope is not None:
+                if self.registry.has():
+                    raise exceptions.InvalidRequestError("Scoped session is already present; no new arguments may be specified.")
+                else:
+                    sess = self.session_factory(**kwargs)
+                    self.registry.set(sess)
+                    return sess
+            else:
+                return self.session_factory(**kwargs)
+        else:
+            return self.registry()
+
+    def configure(self, **kwargs):
+        """reconfigure the sessionmaker used by this SessionContext"""
+        self.session_factory.configure(**kwargs)
+
+def instrument(name):
+    def do(self, *args, **kwargs):
+        return getattr(self.registry(), name)(*args, **kwargs)
+    return do
+for meth in ('get', 'close', 'save', 'commit', 'update', 'flush', 'query', 'delete'):
+    setattr(ScopedSession, meth, instrument(meth))
+
+def makeprop(name):
+    def set(self, attr):
+        setattr(self.registry(), name, attr)
+    def get(self):
+        return getattr(self.registry(), name)
+    return property(get, set)
+for prop in ('bind', 'dirty', 'identity_map'):
+    setattr(ScopedSession, prop, makeprop(prop))
+
+def clslevel(name):
+    def do(cls, *args,**kwargs):
+        return getattr(Session, name)(*args, **kwargs)
+    return classmethod(do)
+for prop in ('close_all',):
+    setattr(ScopedSession, prop, clslevel(prop))
+    
+class _ScopedExt(MapperExtension):
+    def __init__(self, context):
+        self.context = context
+        
+    def get_session(self):
+        return self.context.registry()
+
+    def instrument_class(self, mapper, class_):
+        class query(object):
+            def __getattr__(self, key):
+                return getattr(registry().query(class_), key)
+            def __call__(self):
+                return registry().query(class_)
+
+        if not hasattr(class_, 'query'): 
+            class_.query = query()
+        
+    def init_instance(self, mapper, class_, oldinit, instance, args, kwargs):
+        session = kwargs.pop('_sa_session', self.context.registry())
+        if not isinstance(oldinit, types.MethodType):
+            for key, value in kwargs.items():
+                #if validate:
+                #    if not self.mapper.get_property(key, resolve_synonyms=False, raiseerr=False):
+                #        raise exceptions.ArgumentError("Invalid __init__ argument: '%s'" % key)
+                setattr(instance, key, value)
+        session._save_impl(instance, entity_name=kwargs.pop('_sa_entity_name', None))
+        return EXT_CONTINUE
+
+    def init_failed(self, mapper, class_, oldinit, instance, args, kwargs):
+        object_session(instance).expunge(instance)
+        return EXT_CONTINUE
+
+    def dispose_class(self, mapper, class_):
+        if hasattr(class_, '__init__') and hasattr(class_.__init__, '_oldinit'):
+            if class_.__init__._oldinit is not None:
+                class_.__init__ = class_.__init__._oldinit
+            else:
+                delattr(class_, '__init__')
+        if hasattr(class_, 'query'):
+            delattr(class_, 'query')
+            
+
+            
index 996c7d8a0586c7ca0557dbb45dd0912e780bed51..f982da5368c318a6892809bde8c1833f3c96328d 100644 (file)
@@ -14,93 +14,31 @@ from sqlalchemy.orm.mapper import global_extensions
 
 __all__ = ['Session', 'SessionTransaction']
 
-def sessionmaker(autoflush, transactional, bind=None, scope=None, enhance_classes=False, **kwargs):
+def sessionmaker(autoflush=True, transactional=True, bind=None, **kwargs):
     """Generate a Session configuration."""
+    
+    kwargs['bind'] = bind
+    kwargs['autoflush'] = autoflush
+    kwargs['transactional'] = transactional
 
-    if enhance_classes and scope is None:
-        raise exceptions.InvalidRequestError("enhance_classes requires a non-None 'scope' argument, so that mappers can automatically locate a Session already in progress.")
-        
     class Sess(Session):
         def __init__(self, **local_kwargs):
-            local_kwargs.setdefault('bind', bind)
-            local_kwargs.setdefault('autoflush', autoflush)
-            local_kwargs.setdefault('transactional', transactional)
             for k in kwargs:
                 local_kwargs.setdefault(k, kwargs[k])
             super(Sess, self).__init__(**local_kwargs)
         
-    if scope=="thread":
-        registry = util.ScopedRegistry(Sess, scopefunc=None)
-
-        if enhance_classes:
-            class SessionContextExt(MapperExtension):
-                def get_session(self):
-                    return registry()
-
-                def instrument_class(self, mapper, class_):
-                    class query(object):
-                        def __getattr__(self, key):
-                            return getattr(registry().query(class_), key)
-                        def __call__(self):
-                            return registry().query(class_)
-
-                    if not hasattr(class_, 'query'): 
-                        class_.query = query()
-                    
-                def init_instance(self, mapper, class_, oldinit, instance, args, kwargs):
-                    session = kwargs.pop('_sa_session', registry())
-                    if not isinstance(oldinit, types.MethodType):
-                        for key, value in kwargs.items():
-                            #if validate:
-                            #    if not self.mapper.get_property(key, resolve_synonyms=False, raiseerr=False):
-                            #        raise exceptions.ArgumentError("Invalid __init__ argument: '%s'" % key)
-                            setattr(instance, key, value)
-                    session._save_impl(instance, entity_name=kwargs.pop('_sa_entity_name', None))
-                    return EXT_CONTINUE
-
-                def init_failed(self, mapper, class_, oldinit, instance, args, kwargs):
-                    object_session(instance).expunge(instance)
-                    return EXT_CONTINUE
-
-                def dispose_class(self, mapper, class_):
-                    if hasattr(class_, '__init__') and hasattr(class_.__init__, '_oldinit'):
-                        if class_.__init__._oldinit is not None:
-                            class_.__init__ = class_.__init__._oldinit
-                        else:
-                            delattr(class_, '__init__')
-                    if hasattr(class_, 'query'):
-                        delattr(class_, 'query')
-                        
-            global_extensions.append(SessionContextExt())
+        def configure(self, **new_kwargs):
+            """(re)configure the arguments for this sessionmaker.
             
-        default_scope=scope
-        class ScopedSess(Sess):
-            def __new__(cls, **kwargs):
-                if len(kwargs):
-                    scope = kwargs.pop('scope', default_scope)
-                    if scope is not None:
-                        if registry.has():
-                            raise exceptions.InvalidRequestError("Scoped session is already present; no new arguments may be specified.")
-                        else:
-                            sess = Sess(**kwargs)
-                            registry.set(sess)
-                            return sess
-                    else:
-                        return Sess(**kwargs)
-                else:
-                    return registry()
-        def instrument(name):
-            def do(cls, *args, **kwargs):
-                return getattr(registry(), name)(*args, **kwargs)
-            return classmethod(do)
-        for meth in ('get', 'close', 'save', 'commit', 'update', 'flush', 'query', 'delete'):
-            setattr(ScopedSess, meth, instrument(meth))
+            e.g.
+                Session = sessionmaker()
+                Session.configure(bind=create_engine('sqlite://'))
+            """
             
-        return ScopedSess
-    elif scope is not None:
-        raise exceptions.ArgumentError("Unknown scope '%s'" % scope)
-    else:
-        return session
+            kwargs.update(new_kwargs)
+        configure = classmethod(configure)
+        
+    return Sess
     
 class SessionTransaction(object):
     """Represents a Session-level Transaction.
diff --git a/lib/sqlalchemy/orm/sessionmaker.py b/lib/sqlalchemy/orm/sessionmaker.py
deleted file mode 100644 (file)
index 13e28fd..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-import types
-
-from sqlalchemy import util, exceptions
-from sqlalchemy.orm.session import Session
-from sqlalchemy.orm import query, util as mapperutil, MapperExtension, EXT_CONTINUE
-from sqlalchemy.orm.mapper import global_extensions
-
-def sessionmaker(autoflush, transactional, bind=None, scope=None, enhance_classes=False, **kwargs):
-    """Generate a Session configuration."""
-
-    if enhance_classes and scope is None:
-        raise exceptions.InvalidRequestError("enhance_classes requires a non-None 'scope' argument, so that mappers can automatically locate a Session already in progress.")
-        
-    class Sess(Session):
-        def __init__(self, **local_kwargs):
-            local_kwargs.setdefault('bind', bind)
-            local_kwargs.setdefault('autoflush', autoflush)
-            local_kwargs.setdefault('transactional', transactional)
-            for k in kwargs:
-                local_kwargs.setdefault(k, kwargs[k])
-            super(Sess, self).__init__(**local_kwargs)
-        
-    if scope=="thread":
-        registry = util.ScopedRegistry(Sess, scopefunc=None)
-
-        if enhance_classes:
-            class SessionContextExt(MapperExtension):
-                def get_session(self):
-                    return registry()
-
-                def instrument_class(self, mapper, class_):
-                    class query(object):
-                        def __getattr__(self, key):
-                            return getattr(registry().query(class_), key)
-                        def __call__(self):
-                            return registry().query(class_)
-
-                    if not hasattr(class_, 'query'): 
-                        class_.query = query()
-                    
-                def init_instance(self, mapper, class_, oldinit, instance, args, kwargs):
-                    session = kwargs.pop('_sa_session', registry())
-                    if not isinstance(oldinit, types.MethodType):
-                        for key, value in kwargs.items():
-                            #if validate:
-                            #    if not self.mapper.get_property(key, resolve_synonyms=False, raiseerr=False):
-                            #        raise exceptions.ArgumentError("Invalid __init__ argument: '%s'" % key)
-                            setattr(instance, key, value)
-                    session._save_impl(instance, entity_name=kwargs.pop('_sa_entity_name', None))
-                    return EXT_CONTINUE
-
-                def init_failed(self, mapper, class_, oldinit, instance, args, kwargs):
-                    object_session(instance).expunge(instance)
-                    return EXT_CONTINUE
-
-                def dispose_class(self, mapper, class_):
-                    if hasattr(class_, '__init__') and hasattr(class_.__init__, '_oldinit'):
-                        if class_.__init__._oldinit is not None:
-                            class_.__init__ = class_.__init__._oldinit
-                        else:
-                            delattr(class_, '__init__')
-                    if hasattr(class_, 'query'):
-                        delattr(class_, 'query')
-                        
-            global_extensions.append(SessionContextExt())
-            
-        default_scope=scope
-        
-        class ScopedSess(Sess):
-            def __call__(self, **kwargs):
-                if len(kwargs):
-                    scope = kwargs.pop('scope', default_scope)
-                    if scope is not None:
-                        if registry.has():
-                            raise exceptions.InvalidRequestError("Scoped session is already present; no new arguments may be specified.")
-                        else:
-                            sess = Sess(**kwargs)
-                            registry.set(sess)
-                            return sess
-                    else:
-                        return Sess(**kwargs)
-                else:
-                    return registry()
-                    
-        def instrument(name):
-            def do(cls, *args, **kwargs):
-                return getattr(registry(), name)(*args, **kwargs)
-            return do
-        for meth in ('get', 'close', 'save', 'commit', 'update', 'flush', 'query', 'delete'):
-            setattr(ScopedSess, meth, instrument(meth))
-            
-        def makeprop(name):
-            def set(self, attr):
-                setattr(registry(), name, attr)
-            def get(self):
-                return getattr(registry(), name)
-            return property(get, set)
-        for prop in ('bind', 'dirty', 'identity_map'):
-            setattr(ScopedSess, prop, makeprop(prop))
-            
-        return ScopedSess()
-    elif scope is not None:
-        raise exceptions.ArgumentError("Unknown scope '%s'" % scope)
-    else:
-        return Sess
index 293021b2cfde1c62958a8a50f396ad3fb48884d8..765c360c97d8d27c48e3b89d7077beef24bd38f9 100644 (file)
@@ -13,7 +13,7 @@ from testlib import tables
 class UnitOfWorkTest(AssertMixin):
     def setUpAll(self):
         global Session
-        Session = sessionmaker(autoflush=True, transactional=True, enhance_classes=True, scope="thread")
+        Session = scoped_session(sessionmaker(autoflush=True, transactional=True), enhance_classes=True)
     def tearDownAll(self):
         global_extensions[:] = []
     def tearDown(self):