summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2007-08-02 05:42:49 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2007-08-02 05:42:49 +0000
commitb8b51fe4379936fe142c875ea0f17da14a12c27d (patch)
tree987fc4033cad747f0decfa80e38708a13d812d4c
parent9f23ec7423e98305f43a0b7a7ef894da74325329 (diff)
downloadsqlalchemy-b8b51fe4379936fe142c875ea0f17da14a12c27d.tar.gz
- sessionmaker module is out, replaced with simple function in session.py
- 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()
-rw-r--r--doc/build/content/datamapping.txt6
-rw-r--r--lib/sqlalchemy/ext/assignmapper.py1
-rw-r--r--lib/sqlalchemy/ext/sessioncontext.py67
-rw-r--r--lib/sqlalchemy/orm/__init__.py10
-rw-r--r--lib/sqlalchemy/orm/scoping.py111
-rw-r--r--lib/sqlalchemy/orm/session.py92
-rw-r--r--lib/sqlalchemy/orm/sessionmaker.py105
-rw-r--r--test/orm/unitofwork.py2
8 files changed, 156 insertions, 238 deletions
diff --git a/doc/build/content/datamapping.txt b/doc/build/content/datamapping.txt
index 6308c8d7b..37afa0fd5 100644
--- a/doc/build/content/datamapping.txt
+++ b/doc/build/content/datamapping.txt
@@ -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}
diff --git a/lib/sqlalchemy/ext/assignmapper.py b/lib/sqlalchemy/ext/assignmapper.py
index cab5a9eae..730b5313b 100644
--- a/lib/sqlalchemy/ext/assignmapper.py
+++ b/lib/sqlalchemy/ext/assignmapper.py
@@ -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)
diff --git a/lib/sqlalchemy/ext/sessioncontext.py b/lib/sqlalchemy/ext/sessioncontext.py
index 8221bc495..5ac8acb40 100644
--- a/lib/sqlalchemy/ext/sessioncontext.py
+++ b/lib/sqlalchemy/ext/sessioncontext.py
@@ -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)
+
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py
index 26b583bb1..646135e8c 100644
--- a/lib/sqlalchemy/orm/__init__.py
+++ b/lib/sqlalchemy/orm/__init__.py
@@ -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
index 000000000..3ae63b49a
--- /dev/null
+++ b/lib/sqlalchemy/orm/scoping.py
@@ -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')
+
+
+
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index 996c7d8a0..f982da536 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -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
index 13e28fd1a..000000000
--- a/lib/sqlalchemy/orm/sessionmaker.py
+++ /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
diff --git a/test/orm/unitofwork.py b/test/orm/unitofwork.py
index 293021b2c..765c360c9 100644
--- a/test/orm/unitofwork.py
+++ b/test/orm/unitofwork.py
@@ -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):