diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2007-08-02 05:42:49 +0000 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2007-08-02 05:42:49 +0000 |
commit | b8b51fe4379936fe142c875ea0f17da14a12c27d (patch) | |
tree | 987fc4033cad747f0decfa80e38708a13d812d4c | |
parent | 9f23ec7423e98305f43a0b7a7ef894da74325329 (diff) | |
download | sqlalchemy-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.txt | 6 | ||||
-rw-r--r-- | lib/sqlalchemy/ext/assignmapper.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/ext/sessioncontext.py | 67 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/__init__.py | 10 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/scoping.py | 111 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/session.py | 92 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/sessionmaker.py | 105 | ||||
-rw-r--r-- | test/orm/unitofwork.py | 2 |
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): |