mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-30 04:24:38 -04:00
merge from default
This commit is contained in:
Vendored
+90
-20
@@ -6,6 +6,96 @@
|
||||
.. changelog::
|
||||
:version: 0.8.1
|
||||
|
||||
.. change::
|
||||
:tags: bug, orm
|
||||
:tickets: 2708
|
||||
|
||||
Improved the behavior of instance management regarding
|
||||
the creation of strong references within the Session;
|
||||
an object will no longer have an internal reference cycle
|
||||
created if it's in the transient state or moves into the
|
||||
detached state - the strong ref is created only when the
|
||||
object is attached to a Session and is removed when the
|
||||
object is detached. This makes it somewhat safer for an
|
||||
object to have a `__del__()` method, even though this is
|
||||
not recommended, as relationships with backrefs produce
|
||||
cycles too. A warning has been added when a class with
|
||||
a `__del__()` method is mapped.
|
||||
|
||||
.. change::
|
||||
:tags: bug, sql
|
||||
:tickets: 2702
|
||||
|
||||
A major fix to the way in which a select() object produces
|
||||
labeled columns when apply_labels() is used; this mode
|
||||
produces a SELECT where each column is labeled as in
|
||||
<tablename>_<columnname>, to remove column name collisions
|
||||
for a multiple table select. The fix is that if two labels
|
||||
collide when combined with the table name, i.e.
|
||||
"foo.bar_id" and "foo_bar.id", anonymous aliasing will be
|
||||
applied to one of the dupes. This allows the ORM to handle
|
||||
both columns independently; previously, 0.7
|
||||
would in some cases silently emit a second SELECT for the
|
||||
column that was "duped", and in 0.8 an ambiguous column error
|
||||
would be emitted. The "keys" applied to the .c. collection
|
||||
of the select() will also be deduped, so that the "column
|
||||
being replaced" warning will no longer emit for any select()
|
||||
that specifies use_labels, though the dupe key will be given
|
||||
an anonymous label which isn't generally user-friendly.
|
||||
|
||||
.. change::
|
||||
:tags: bug, mysql
|
||||
:pullreq: 54
|
||||
|
||||
Updated a regexp to correctly extract error code on
|
||||
google app engine v1.7.5 and newer. Courtesy
|
||||
Dan Ring.
|
||||
|
||||
.. change::
|
||||
:tags: bug, examples
|
||||
|
||||
Fixed a long-standing bug in the caching example, where
|
||||
the limit/offset parameter values wouldn't be taken into
|
||||
account when computing the cache key. The
|
||||
_key_from_query() function has been simplified to work
|
||||
directly from the final compiled statement in order to get
|
||||
at both the full statement as well as the fully processed
|
||||
parameter list.
|
||||
|
||||
.. change::
|
||||
:tags: bug, mssql
|
||||
:tickets: 2355
|
||||
|
||||
Part of a longer series of fixes needed for pyodbc+
|
||||
mssql, a CAST to NVARCHAR(max) has been added to the bound
|
||||
parameter for the table name and schema name in all information schema
|
||||
queries to avoid the issue of comparing NVARCHAR to NTEXT,
|
||||
which seems to be rejected by the ODBC driver in some cases,
|
||||
such as FreeTDS (0.91 only?) plus unicode bound parameters being passed.
|
||||
The issue seems to be specific to the SQL Server information
|
||||
schema tables and the workaround is harmless for those cases
|
||||
where the problem doesn't exist in the first place.
|
||||
|
||||
.. change::
|
||||
:tags: bug, sql
|
||||
:tickets: 2691
|
||||
|
||||
Fixed bug where disconnect detect on error would
|
||||
raise an attribute error if the error were being
|
||||
raised after the Connection object had already
|
||||
been closed.
|
||||
|
||||
.. change::
|
||||
:tags: bug, sql
|
||||
:tickets: 2703
|
||||
|
||||
Reworked internal exception raises that emit
|
||||
a rollback() before re-raising, so that the stack
|
||||
trace is preserved from sys.exc_info() before entering
|
||||
the rollback. This so that the traceback is preserved
|
||||
when using coroutine frameworks which may have switched
|
||||
contexts before the rollback function returns.
|
||||
|
||||
.. change::
|
||||
:tags: bug, orm
|
||||
:tickets: 2697
|
||||
@@ -49,26 +139,6 @@
|
||||
handling routine fails and regardless of whether the
|
||||
condition is a disconnect or not.
|
||||
|
||||
.. change::
|
||||
:tags: bug, sql
|
||||
:tickets: 2702
|
||||
|
||||
A major fix to the way in which a select() object produces
|
||||
labeled columns when apply_labels() is used; this mode
|
||||
produces a SELECT where each column is labeled as in
|
||||
<tablename>_<columnname>, to remove column name collisions
|
||||
for a multiple table select. The fix is that if two labels
|
||||
collide when combined with the table name, i.e.
|
||||
"foo.bar_id" and "foo_bar.id", anonymous aliasing will be
|
||||
applied to one of the dupes. This allows the ORM to handle
|
||||
both columns independently; previously, 0.7
|
||||
would in some cases silently emit a second SELECT for the
|
||||
column that was "duped", and in 0.8 an ambiguous column error
|
||||
would be emitted. The "keys" applied to the .c. collection
|
||||
of the select() will also be deduped, so that the "column
|
||||
being replaced" warning will no longer emit for any select()
|
||||
that specifies use_labels, though the dupe key will be given
|
||||
an anonymous label which isn't generally user-friendly.
|
||||
|
||||
.. change::
|
||||
:tags: bug, orm, declarative
|
||||
|
||||
Vendored
+5
@@ -175,6 +175,11 @@ MySQL-Connector
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.mysql.mysqlconnector
|
||||
|
||||
cymysql
|
||||
------------
|
||||
|
||||
.. automodule:: sqlalchemy.dialects.mysql.cymysql
|
||||
|
||||
Google App Engine
|
||||
-----------------------
|
||||
|
||||
|
||||
@@ -136,24 +136,15 @@ def _key_from_query(query, qualifier=None):
|
||||
|
||||
"""
|
||||
|
||||
v = []
|
||||
def visit_bindparam(bind):
|
||||
|
||||
if bind.key in query._params:
|
||||
value = query._params[bind.key]
|
||||
elif bind.callable:
|
||||
value = bind.callable()
|
||||
else:
|
||||
value = bind.value
|
||||
|
||||
v.append(unicode(value))
|
||||
|
||||
stmt = query.statement
|
||||
visitors.traverse(stmt, {}, {'bindparam': visit_bindparam})
|
||||
compiled = stmt.compile()
|
||||
params = compiled.params
|
||||
|
||||
# here we return the key as a long string. our "key mangler"
|
||||
# set up with the region will boil it down to an md5.
|
||||
return " ".join([unicode(stmt)] + v)
|
||||
return " ".join(
|
||||
[unicode(compiled)] +
|
||||
[unicode(params[k]) for k in sorted(params)])
|
||||
|
||||
class FromCache(MapperOption):
|
||||
"""Specifies that a Query should load results from a cache."""
|
||||
@@ -181,24 +172,6 @@ class FromCache(MapperOption):
|
||||
"""Process a Query during normal loading operation."""
|
||||
query._cache_region = self
|
||||
|
||||
class RelationshipCache(MapperOption):
|
||||
"""Specifies that a Query as called within a "lazy load"
|
||||
should load results from a cache."""
|
||||
|
||||
propagate_to_loaders = True
|
||||
|
||||
def __init__(self, attribute, region="default"):
|
||||
self.region = region
|
||||
self.cls_ = attribute.property.parent.class_
|
||||
self.key = attribute.property.key
|
||||
|
||||
def process_query_conditionally(self, query):
|
||||
if query._current_path:
|
||||
mapper, key = query._current_path[-2:]
|
||||
if issubclass(mapper.class_, self.cls_) and \
|
||||
key == self.key:
|
||||
query._cache_region = self
|
||||
|
||||
class RelationshipCache(MapperOption):
|
||||
"""Specifies that a Query as called within a "lazy load"
|
||||
should load results from a cache."""
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
from ... import Table, MetaData, Column
|
||||
from ...types import String, Unicode, Integer, TypeDecorator
|
||||
from ... import cast
|
||||
|
||||
ischema = MetaData()
|
||||
|
||||
@@ -22,6 +23,9 @@ class CoerceUnicode(TypeDecorator):
|
||||
# end Py2K
|
||||
return value
|
||||
|
||||
def bind_expression(self, bindvalue):
|
||||
return cast(bindvalue, Unicode)
|
||||
|
||||
schemata = Table("SCHEMATA", ischema,
|
||||
Column("CATALOG_NAME", CoerceUnicode, key="catalog_name"),
|
||||
Column("SCHEMA_NAME", CoerceUnicode, key="schema_name"),
|
||||
|
||||
@@ -65,10 +65,10 @@ class MySQLDialect_gaerdbms(MySQLDialect_mysqldb):
|
||||
return [], opts
|
||||
|
||||
def _extract_error_code(self, exception):
|
||||
match = re.compile(r"^(\d+):").match(str(exception))
|
||||
match = re.compile(r"^(\d+):|^\((\d+),").match(str(exception))
|
||||
# The rdbms api will wrap then re-raise some types of errors
|
||||
# making this regex return no matches.
|
||||
code = match.group(1) if match else None
|
||||
code = match.group(1) or match.group(2) if match else None
|
||||
if code:
|
||||
return int(code)
|
||||
|
||||
|
||||
@@ -462,7 +462,6 @@ class Connection(Connectable):
|
||||
self.engine.dialect.do_begin(self.connection)
|
||||
except Exception, e:
|
||||
self._handle_dbapi_exception(e, None, None, None, None)
|
||||
raise
|
||||
|
||||
def _rollback_impl(self):
|
||||
if self._has_events:
|
||||
@@ -476,7 +475,6 @@ class Connection(Connectable):
|
||||
self.__transaction = None
|
||||
except Exception, e:
|
||||
self._handle_dbapi_exception(e, None, None, None, None)
|
||||
raise
|
||||
else:
|
||||
self.__transaction = None
|
||||
|
||||
@@ -491,7 +489,6 @@ class Connection(Connectable):
|
||||
self.__transaction = None
|
||||
except Exception, e:
|
||||
self._handle_dbapi_exception(e, None, None, None, None)
|
||||
raise
|
||||
|
||||
def _savepoint_impl(self, name=None):
|
||||
if self._has_events:
|
||||
@@ -693,7 +690,6 @@ class Connection(Connectable):
|
||||
dialect, self, conn)
|
||||
except Exception, e:
|
||||
self._handle_dbapi_exception(e, None, None, None, None)
|
||||
raise
|
||||
|
||||
ret = ctx._exec_default(default, None)
|
||||
if self.should_close_with_result:
|
||||
@@ -830,7 +826,6 @@ class Connection(Connectable):
|
||||
self._handle_dbapi_exception(e,
|
||||
str(statement), parameters,
|
||||
None, None)
|
||||
raise
|
||||
|
||||
if context.compiled:
|
||||
context.pre_exec()
|
||||
@@ -877,7 +872,6 @@ class Connection(Connectable):
|
||||
parameters,
|
||||
cursor,
|
||||
context)
|
||||
raise
|
||||
|
||||
if self._has_events:
|
||||
self.dispatch.after_cursor_execute(self, cursor,
|
||||
@@ -952,7 +946,6 @@ class Connection(Connectable):
|
||||
parameters,
|
||||
cursor,
|
||||
None)
|
||||
raise
|
||||
|
||||
def _safe_close_cursor(self, cursor):
|
||||
"""Close the given cursor, catching exceptions
|
||||
@@ -983,22 +976,21 @@ class Connection(Connectable):
|
||||
cursor,
|
||||
context):
|
||||
|
||||
exc_info = sys.exc_info()
|
||||
|
||||
if not self._is_disconnect:
|
||||
self._is_disconnect = isinstance(e, self.dialect.dbapi.Error) and \
|
||||
not self.closed and \
|
||||
self.dialect.is_disconnect(e, self.__connection, cursor)
|
||||
|
||||
if self._reentrant_error:
|
||||
# Py3K
|
||||
#raise exc.DBAPIError.instance(statement, parameters, e,
|
||||
# self.dialect.dbapi.Error) from e
|
||||
# Py2K
|
||||
raise exc.DBAPIError.instance(statement,
|
||||
util.raise_from_cause(
|
||||
exc.DBAPIError.instance(statement,
|
||||
parameters,
|
||||
e,
|
||||
self.dialect.dbapi.Error), \
|
||||
None, sys.exc_info()[2]
|
||||
# end Py2K
|
||||
self.dialect.dbapi.Error),
|
||||
exc_info
|
||||
)
|
||||
self._reentrant_error = True
|
||||
try:
|
||||
# non-DBAPI error - if we already got a context,
|
||||
@@ -1021,26 +1013,18 @@ class Connection(Connectable):
|
||||
self._safe_close_cursor(cursor)
|
||||
self._autorollback()
|
||||
|
||||
if not should_wrap:
|
||||
return
|
||||
if should_wrap:
|
||||
util.raise_from_cause(
|
||||
exc.DBAPIError.instance(
|
||||
statement,
|
||||
parameters,
|
||||
e,
|
||||
self.dialect.dbapi.Error,
|
||||
connection_invalidated=self._is_disconnect),
|
||||
exc_info
|
||||
)
|
||||
|
||||
# Py3K
|
||||
#raise exc.DBAPIError.instance(
|
||||
# statement,
|
||||
# parameters,
|
||||
# e,
|
||||
# self.dialect.dbapi.Error,
|
||||
# connection_invalidated=self._is_disconnect) \
|
||||
# from e
|
||||
# Py2K
|
||||
raise exc.DBAPIError.instance(
|
||||
statement,
|
||||
parameters,
|
||||
e,
|
||||
self.dialect.dbapi.Error,
|
||||
connection_invalidated=self._is_disconnect), \
|
||||
None, sys.exc_info()[2]
|
||||
# end Py2K
|
||||
util.reraise(*exc_info)
|
||||
|
||||
finally:
|
||||
del self._reentrant_error
|
||||
@@ -1115,8 +1099,8 @@ class Connection(Connectable):
|
||||
trans.commit()
|
||||
return ret
|
||||
except:
|
||||
trans.rollback()
|
||||
raise
|
||||
with util.safe_reraise():
|
||||
trans.rollback()
|
||||
|
||||
def run_callable(self, callable_, *args, **kwargs):
|
||||
"""Given a callable object or function, execute it, passing
|
||||
@@ -1222,8 +1206,8 @@ class Transaction(object):
|
||||
try:
|
||||
self.commit()
|
||||
except:
|
||||
self.rollback()
|
||||
raise
|
||||
with util.safe_reraise():
|
||||
self.rollback()
|
||||
else:
|
||||
self.rollback()
|
||||
|
||||
@@ -1548,8 +1532,8 @@ class Engine(Connectable, log.Identified):
|
||||
try:
|
||||
trans = conn.begin()
|
||||
except:
|
||||
conn.close()
|
||||
raise
|
||||
with util.safe_reraise():
|
||||
conn.close()
|
||||
return Engine._trans_ctx(conn, trans, close_with_result)
|
||||
|
||||
def transaction(self, callable_, *args, **kwargs):
|
||||
|
||||
@@ -737,7 +737,6 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
|
||||
except Exception, e:
|
||||
self.root_connection._handle_dbapi_exception(
|
||||
e, None, None, None, self)
|
||||
raise
|
||||
else:
|
||||
inputsizes = {}
|
||||
for key in self.compiled.bind_names.values():
|
||||
@@ -756,7 +755,6 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
|
||||
except Exception, e:
|
||||
self.root_connection._handle_dbapi_exception(
|
||||
e, None, None, None, self)
|
||||
raise
|
||||
|
||||
def _exec_default(self, default, type_):
|
||||
if default.is_sequence:
|
||||
|
||||
@@ -443,7 +443,6 @@ class ResultProxy(object):
|
||||
except Exception, e:
|
||||
self.connection._handle_dbapi_exception(
|
||||
e, None, None, self.cursor, self.context)
|
||||
raise
|
||||
|
||||
@property
|
||||
def lastrowid(self):
|
||||
@@ -467,7 +466,6 @@ class ResultProxy(object):
|
||||
self.connection._handle_dbapi_exception(
|
||||
e, None, None,
|
||||
self._saved_cursor, self.context)
|
||||
raise
|
||||
|
||||
@property
|
||||
def returns_rows(self):
|
||||
@@ -752,7 +750,6 @@ class ResultProxy(object):
|
||||
self.connection._handle_dbapi_exception(
|
||||
e, None, None,
|
||||
self.cursor, self.context)
|
||||
raise
|
||||
|
||||
def fetchmany(self, size=None):
|
||||
"""Fetch many rows, just like DB-API
|
||||
@@ -772,7 +769,6 @@ class ResultProxy(object):
|
||||
self.connection._handle_dbapi_exception(
|
||||
e, None, None,
|
||||
self.cursor, self.context)
|
||||
raise
|
||||
|
||||
def fetchone(self):
|
||||
"""Fetch one row, just like DB-API ``cursor.fetchone()``.
|
||||
@@ -792,7 +788,6 @@ class ResultProxy(object):
|
||||
self.connection._handle_dbapi_exception(
|
||||
e, None, None,
|
||||
self.cursor, self.context)
|
||||
raise
|
||||
|
||||
def first(self):
|
||||
"""Fetch the first row and then close the result set unconditionally.
|
||||
@@ -809,7 +804,6 @@ class ResultProxy(object):
|
||||
self.connection._handle_dbapi_exception(
|
||||
e, None, None,
|
||||
self.cursor, self.context)
|
||||
raise
|
||||
|
||||
try:
|
||||
if row is not None:
|
||||
|
||||
@@ -72,6 +72,13 @@ class ClassManager(dict):
|
||||
self.manage()
|
||||
self._instrument_init()
|
||||
|
||||
if '__del__' in class_.__dict__:
|
||||
util.warn("__del__() method on class %s will "
|
||||
"cause unreachable cycles and memory leaks, "
|
||||
"as SQLAlchemy instrumentation often creates "
|
||||
"reference cycles. Please remove this method." %
|
||||
class_)
|
||||
|
||||
dispatch = event.dispatcher(events.InstanceEvents)
|
||||
|
||||
@property
|
||||
|
||||
@@ -761,8 +761,9 @@ class Mapper(_InspectionAttr):
|
||||
del self._configure_failed
|
||||
|
||||
if not self.non_primary and \
|
||||
self.class_manager is not None and \
|
||||
self.class_manager.is_mapped and \
|
||||
self.class_manager.mapper is self:
|
||||
self.class_manager.mapper is self:
|
||||
instrumentation.unregister_class(self.class_)
|
||||
|
||||
def _configure_pks(self):
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Provides the Session class and related utilities."""
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import weakref
|
||||
from .. import util, sql, engine, exc as sa_exc, event
|
||||
from ..sql import util as sql_util, expression
|
||||
@@ -341,8 +342,8 @@ class SessionTransaction(object):
|
||||
for t in set(self._connections.values()):
|
||||
t[1].prepare()
|
||||
except:
|
||||
self.rollback()
|
||||
raise
|
||||
with util.safe_reraise():
|
||||
self.rollback()
|
||||
|
||||
self._state = PREPARED
|
||||
|
||||
@@ -441,8 +442,8 @@ class SessionTransaction(object):
|
||||
try:
|
||||
self.commit()
|
||||
except:
|
||||
self.rollback()
|
||||
raise
|
||||
with util.safe_reraise():
|
||||
self.rollback()
|
||||
else:
|
||||
self.rollback()
|
||||
|
||||
@@ -1726,13 +1727,13 @@ class Session(_SessionClassMethods):
|
||||
|
||||
def _before_attach(self, state):
|
||||
if state.session_id != self.hash_key and \
|
||||
self.dispatch.before_attach:
|
||||
self.dispatch.before_attach:
|
||||
self.dispatch.before_attach(self, state.obj())
|
||||
|
||||
def _attach(self, state, include_before=False):
|
||||
if state.key and \
|
||||
state.key in self.identity_map and \
|
||||
not self.identity_map.contains_state(state):
|
||||
not self.identity_map.contains_state(state):
|
||||
raise sa_exc.InvalidRequestError("Can't attach instance "
|
||||
"%s; another instance with key %s is already "
|
||||
"present in this session."
|
||||
@@ -1748,9 +1749,11 @@ class Session(_SessionClassMethods):
|
||||
|
||||
if state.session_id != self.hash_key:
|
||||
if include_before and \
|
||||
self.dispatch.before_attach:
|
||||
self.dispatch.before_attach:
|
||||
self.dispatch.before_attach(self, state.obj())
|
||||
state.session_id = self.hash_key
|
||||
if state.modified and state._strong_obj is None:
|
||||
state._strong_obj = state.obj()
|
||||
if self.dispatch.after_attach:
|
||||
self.dispatch.after_attach(self, state.obj())
|
||||
|
||||
@@ -1928,8 +1931,8 @@ class Session(_SessionClassMethods):
|
||||
transaction.commit()
|
||||
|
||||
except:
|
||||
transaction.rollback(_capture_exception=True)
|
||||
raise
|
||||
with util.safe_reraise():
|
||||
transaction.rollback(_capture_exception=True)
|
||||
|
||||
def is_modified(self, instance, include_collections=True,
|
||||
passive=True):
|
||||
|
||||
+14
-13
@@ -164,7 +164,7 @@ class InstanceState(interfaces._InspectionAttr):
|
||||
return bool(self.key)
|
||||
|
||||
def _detach(self):
|
||||
self.session_id = None
|
||||
self.session_id = self._strong_obj = None
|
||||
|
||||
def _dispose(self):
|
||||
self._detach()
|
||||
@@ -176,7 +176,7 @@ class InstanceState(interfaces._InspectionAttr):
|
||||
instance_dict.discard(self)
|
||||
|
||||
self.callables = {}
|
||||
self.session_id = None
|
||||
self.session_id = self._strong_obj = None
|
||||
del self.obj
|
||||
|
||||
def obj(self):
|
||||
@@ -259,9 +259,6 @@ class InstanceState(interfaces._InspectionAttr):
|
||||
self.expired = state.get('expired', False)
|
||||
self.callables = state.get('callables', {})
|
||||
|
||||
if self.modified:
|
||||
self._strong_obj = inst
|
||||
|
||||
self.__dict__.update([
|
||||
(k, state[k]) for k in (
|
||||
'key', 'load_options',
|
||||
@@ -322,6 +319,7 @@ class InstanceState(interfaces._InspectionAttr):
|
||||
modified_set.discard(self)
|
||||
|
||||
self.modified = False
|
||||
self._strong_obj = None
|
||||
|
||||
self.committed_state.clear()
|
||||
|
||||
@@ -335,7 +333,7 @@ class InstanceState(interfaces._InspectionAttr):
|
||||
for key in self.manager:
|
||||
impl = self.manager[key].impl
|
||||
if impl.accepts_scalar_loader and \
|
||||
(impl.expire_missing or key in dict_):
|
||||
(impl.expire_missing or key in dict_):
|
||||
self.callables[key] = self
|
||||
old = dict_.pop(key, None)
|
||||
if impl.collection and old is not None:
|
||||
@@ -435,18 +433,22 @@ class InstanceState(interfaces._InspectionAttr):
|
||||
|
||||
self.committed_state[attr.key] = previous
|
||||
|
||||
# the "or not self.modified" is defensive at
|
||||
# this point. The assertion below is expected
|
||||
# to be True:
|
||||
# assert self._strong_obj is None or self.modified
|
||||
|
||||
if self._strong_obj is None or not self.modified:
|
||||
if (self.session_id and self._strong_obj is None) \
|
||||
or not self.modified:
|
||||
instance_dict = self._instance_dict()
|
||||
if instance_dict:
|
||||
instance_dict._modified.add(self)
|
||||
|
||||
self._strong_obj = self.obj()
|
||||
if self._strong_obj is None:
|
||||
# only create _strong_obj link if attached
|
||||
# to a session
|
||||
|
||||
inst = self.obj()
|
||||
if self.session_id:
|
||||
self._strong_obj = inst
|
||||
|
||||
if inst is None:
|
||||
raise orm_exc.ObjectDereferencedError(
|
||||
"Can't emit change event for attribute '%s' - "
|
||||
"parent object of type %s has been garbage "
|
||||
@@ -467,7 +469,6 @@ class InstanceState(interfaces._InspectionAttr):
|
||||
this step if a value was not populated in state.dict.
|
||||
|
||||
"""
|
||||
class_manager = self.manager
|
||||
for key in keys:
|
||||
self.committed_state.pop(key, None)
|
||||
|
||||
|
||||
@@ -778,11 +778,12 @@ class SubqueryLoader(AbstractRelationshipLoader):
|
||||
# to look only for significant columns
|
||||
q = orig_query._clone().correlate(None)
|
||||
|
||||
# TODO: why does polymporphic etc. require hardcoding
|
||||
# into _adapt_col_list ? Does query.add_columns(...) work
|
||||
# with polymorphic loading ?
|
||||
if entity_mapper.isa(leftmost_mapper):
|
||||
# set a real "from" if not present, as this is more
|
||||
# accurate than just going off of the column expression
|
||||
if not q._from_obj and entity_mapper.isa(leftmost_mapper):
|
||||
q._set_select_from(entity_mapper)
|
||||
|
||||
# select from the identity columns of the outer
|
||||
q._set_entities(q._adapt_col_list(leftmost_attr))
|
||||
|
||||
if q._order_by is False:
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
from .compat import callable, cmp, reduce, \
|
||||
threading, py3k, py3k_warning, jython, pypy, cpython, win32, set_types, \
|
||||
pickle, dottedgetter, parse_qsl, namedtuple, next, WeakSet
|
||||
pickle, dottedgetter, parse_qsl, namedtuple, next, WeakSet, reraise, \
|
||||
raise_from_cause
|
||||
|
||||
from ._collections import KeyedTuple, ImmutableContainer, immutabledict, \
|
||||
Properties, OrderedProperties, ImmutableProperties, OrderedDict, \
|
||||
@@ -26,7 +27,7 @@ from .langhelpers import iterate_attributes, class_hierarchy, \
|
||||
duck_type_collection, assert_arg_type, symbol, dictlike_iteritems,\
|
||||
classproperty, set_creation_order, warn_exception, warn, NoneType,\
|
||||
constructor_copy, methods_equivalent, chop_traceback, asint,\
|
||||
generic_repr, counter, PluginLoader, hybridmethod
|
||||
generic_repr, counter, PluginLoader, hybridmethod, safe_reraise
|
||||
|
||||
from .deprecations import warn_deprecated, warn_pending_deprecation, \
|
||||
deprecated, pending_deprecation
|
||||
|
||||
@@ -140,3 +140,28 @@ else:
|
||||
def b(s):
|
||||
return s
|
||||
|
||||
|
||||
if py3k:
|
||||
def reraise(tp, value, tb=None, cause=None):
|
||||
if cause is not None:
|
||||
value.__cause__ = cause
|
||||
if value.__traceback__ is not tb:
|
||||
raise value.with_traceback(tb)
|
||||
raise value
|
||||
|
||||
def raise_from_cause(exception, exc_info):
|
||||
exc_type, exc_value, exc_tb = exc_info
|
||||
reraise(type(exception), exception, tb=exc_tb, cause=exc_value)
|
||||
else:
|
||||
exec("def reraise(tp, value, tb=None, cause=None):\n"
|
||||
" raise tp, value, tb\n")
|
||||
|
||||
def raise_from_cause(exception, exc_info):
|
||||
# not as nice as that of Py3K, but at least preserves
|
||||
# the code line where the issue occurred
|
||||
exc_type, exc_value, exc_tb = exc_info
|
||||
reraise(type(exception), exception, tb=exc_tb)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ from .compat import set_types, threading, \
|
||||
from functools import update_wrapper
|
||||
from .. import exc
|
||||
import hashlib
|
||||
from . import compat
|
||||
|
||||
def md5_hex(x):
|
||||
# Py3K
|
||||
@@ -28,6 +29,37 @@ def md5_hex(x):
|
||||
m.update(x)
|
||||
return m.hexdigest()
|
||||
|
||||
class safe_reraise(object):
|
||||
"""Reraise an exception after invoking some
|
||||
handler code.
|
||||
|
||||
Stores the existing exception info before
|
||||
invoking so that it is maintained across a potential
|
||||
coroutine context switch.
|
||||
|
||||
e.g.::
|
||||
|
||||
try:
|
||||
sess.commit()
|
||||
except:
|
||||
with safe_reraise():
|
||||
sess.rollback()
|
||||
|
||||
"""
|
||||
|
||||
def __enter__(self):
|
||||
self._exc_info = sys.exc_info()
|
||||
|
||||
def __exit__(self, type_, value, traceback):
|
||||
# see #2703 for notes
|
||||
if type_ is None:
|
||||
exc_type, exc_value, exc_tb = self._exc_info
|
||||
self._exc_info = None # remove potential circular references
|
||||
compat.reraise(exc_type, exc_value, exc_tb)
|
||||
else:
|
||||
self._exc_info = None # remove potential circular references
|
||||
compat.reraise(type_, value, traceback)
|
||||
|
||||
def decode_slice(slc):
|
||||
"""decode a slice object as sent to __getitem__.
|
||||
|
||||
|
||||
@@ -1949,7 +1949,7 @@ class TypeRoundTripTest(fixtures.TestBase, AssertsExecutionResults, ComparesTabl
|
||||
engine.execute(tbl.delete())
|
||||
|
||||
class MonkeyPatchedBinaryTest(fixtures.TestBase):
|
||||
__only_on__ = 'mssql'
|
||||
__only_on__ = 'mssql+pymssql'
|
||||
|
||||
def test_unicode(self):
|
||||
module = __import__('pymssql')
|
||||
|
||||
@@ -51,6 +51,7 @@ class MockCursor(object):
|
||||
def __init__(self, parent):
|
||||
self.explode = parent.explode
|
||||
self.description = ()
|
||||
self.closed = False
|
||||
def execute(self, *args, **kwargs):
|
||||
if self.explode == 'execute':
|
||||
raise MockDisconnect("Lost the DB connection on execute")
|
||||
@@ -60,10 +61,20 @@ class MockCursor(object):
|
||||
elif self.explode in ('rollback', 'rollback_no_disconnect'):
|
||||
raise MockError(
|
||||
"something broke on execute but we didn't lose the connection")
|
||||
elif args and "select" in args[0]:
|
||||
self.description = [('foo', None, None, None, None, None)]
|
||||
else:
|
||||
return
|
||||
def fetchall(self):
|
||||
if self.closed:
|
||||
raise MockError("cursor closed")
|
||||
return []
|
||||
def fetchone(self):
|
||||
if self.closed:
|
||||
raise MockError("cursor closed")
|
||||
return None
|
||||
def close(self):
|
||||
pass
|
||||
self.closed = True
|
||||
|
||||
db, dbapi = None, None
|
||||
class MockReconnectTest(fixtures.TestBase):
|
||||
@@ -294,6 +305,17 @@ class MockReconnectTest(fixtures.TestBase):
|
||||
conn.execute, select([1])
|
||||
)
|
||||
|
||||
def test_check_disconnect_no_cursor(self):
|
||||
conn = db.connect()
|
||||
result = conn.execute("select 1")
|
||||
result.cursor.close()
|
||||
conn.close()
|
||||
assert_raises_message(
|
||||
tsa.exc.DBAPIError,
|
||||
"cursor closed",
|
||||
list, result
|
||||
)
|
||||
|
||||
class CursorErrTest(fixtures.TestBase):
|
||||
|
||||
def setup(self):
|
||||
|
||||
@@ -445,6 +445,20 @@ class MapperInitTest(fixtures.ORMTest):
|
||||
# C is not mapped in the current implementation
|
||||
assert_raises(sa.orm.exc.UnmappedClassError, class_mapper, C)
|
||||
|
||||
def test_del_warning(self):
|
||||
class A(object):
|
||||
def __del__(self):
|
||||
pass
|
||||
|
||||
assert_raises_message(
|
||||
sa.exc.SAWarning,
|
||||
r"__del__\(\) method on class "
|
||||
"<class 'test.orm.test_instrumentation.A'> will cause "
|
||||
"unreachable cycles and memory leaks, as SQLAlchemy "
|
||||
"instrumentation often creates reference cycles. "
|
||||
"Please remove this method.",
|
||||
mapper, A, self.fixture()
|
||||
)
|
||||
|
||||
class OnLoadTest(fixtures.ORMTest):
|
||||
"""Check that Events.load is not hit in regular attributes operations."""
|
||||
|
||||
@@ -857,6 +857,150 @@ class SessionStateWFixtureTest(_fixtures.FixtureTest):
|
||||
assert sa.orm.attributes.instance_state(a).session_id is None
|
||||
|
||||
|
||||
class NoCyclesOnTransientDetachedTest(_fixtures.FixtureTest):
|
||||
"""Test the instance_state._strong_obj link that it
|
||||
is present only on persistent/pending objects and never
|
||||
transient/detached.
|
||||
|
||||
"""
|
||||
run_inserts = None
|
||||
|
||||
def setup(self):
|
||||
mapper(self.classes.User, self.tables.users)
|
||||
|
||||
def _assert_modified(self, u1):
|
||||
assert sa.orm.attributes.instance_state(u1).modified
|
||||
|
||||
def _assert_not_modified(self, u1):
|
||||
assert not sa.orm.attributes.instance_state(u1).modified
|
||||
|
||||
def _assert_cycle(self, u1):
|
||||
assert sa.orm.attributes.instance_state(u1)._strong_obj is not None
|
||||
|
||||
def _assert_no_cycle(self, u1):
|
||||
assert sa.orm.attributes.instance_state(u1)._strong_obj is None
|
||||
|
||||
def _persistent_fixture(self):
|
||||
User = self.classes.User
|
||||
u1 = User()
|
||||
u1.name = "ed"
|
||||
sess = Session()
|
||||
sess.add(u1)
|
||||
sess.flush()
|
||||
return sess, u1
|
||||
|
||||
def test_transient(self):
|
||||
User = self.classes.User
|
||||
u1 = User()
|
||||
u1.name = 'ed'
|
||||
self._assert_no_cycle(u1)
|
||||
self._assert_modified(u1)
|
||||
|
||||
def test_transient_to_pending(self):
|
||||
User = self.classes.User
|
||||
u1 = User()
|
||||
u1.name = 'ed'
|
||||
self._assert_modified(u1)
|
||||
self._assert_no_cycle(u1)
|
||||
sess = Session()
|
||||
sess.add(u1)
|
||||
self._assert_cycle(u1)
|
||||
sess.flush()
|
||||
self._assert_no_cycle(u1)
|
||||
self._assert_not_modified(u1)
|
||||
|
||||
def test_dirty_persistent_to_detached_via_expunge(self):
|
||||
sess, u1 = self._persistent_fixture()
|
||||
u1.name = 'edchanged'
|
||||
self._assert_cycle(u1)
|
||||
sess.expunge(u1)
|
||||
self._assert_no_cycle(u1)
|
||||
|
||||
def test_dirty_persistent_to_detached_via_close(self):
|
||||
sess, u1 = self._persistent_fixture()
|
||||
u1.name = 'edchanged'
|
||||
self._assert_cycle(u1)
|
||||
sess.close()
|
||||
self._assert_no_cycle(u1)
|
||||
|
||||
def test_clean_persistent_to_detached_via_close(self):
|
||||
sess, u1 = self._persistent_fixture()
|
||||
self._assert_no_cycle(u1)
|
||||
self._assert_not_modified(u1)
|
||||
sess.close()
|
||||
u1.name = 'edchanged'
|
||||
self._assert_modified(u1)
|
||||
self._assert_no_cycle(u1)
|
||||
|
||||
def test_detached_to_dirty_deleted(self):
|
||||
sess, u1 = self._persistent_fixture()
|
||||
sess.expunge(u1)
|
||||
u1.name = 'edchanged'
|
||||
self._assert_no_cycle(u1)
|
||||
sess.delete(u1)
|
||||
self._assert_cycle(u1)
|
||||
|
||||
def test_detached_to_dirty_persistent(self):
|
||||
sess, u1 = self._persistent_fixture()
|
||||
sess.expunge(u1)
|
||||
u1.name = 'edchanged'
|
||||
self._assert_modified(u1)
|
||||
self._assert_no_cycle(u1)
|
||||
sess.add(u1)
|
||||
self._assert_cycle(u1)
|
||||
self._assert_modified(u1)
|
||||
|
||||
def test_detached_to_clean_persistent(self):
|
||||
sess, u1 = self._persistent_fixture()
|
||||
sess.expunge(u1)
|
||||
self._assert_no_cycle(u1)
|
||||
self._assert_not_modified(u1)
|
||||
sess.add(u1)
|
||||
self._assert_no_cycle(u1)
|
||||
self._assert_not_modified(u1)
|
||||
|
||||
def test_move_persistent_clean(self):
|
||||
sess, u1 = self._persistent_fixture()
|
||||
sess.close()
|
||||
s2 = Session()
|
||||
s2.add(u1)
|
||||
self._assert_no_cycle(u1)
|
||||
self._assert_not_modified(u1)
|
||||
|
||||
def test_move_persistent_dirty(self):
|
||||
sess, u1 = self._persistent_fixture()
|
||||
u1.name = 'edchanged'
|
||||
self._assert_cycle(u1)
|
||||
self._assert_modified(u1)
|
||||
sess.close()
|
||||
self._assert_no_cycle(u1)
|
||||
s2 = Session()
|
||||
s2.add(u1)
|
||||
self._assert_cycle(u1)
|
||||
self._assert_modified(u1)
|
||||
|
||||
@testing.requires.predictable_gc
|
||||
def test_move_gc_session_persistent_dirty(self):
|
||||
sess, u1 = self._persistent_fixture()
|
||||
u1.name = 'edchanged'
|
||||
self._assert_cycle(u1)
|
||||
self._assert_modified(u1)
|
||||
del sess
|
||||
gc_collect()
|
||||
self._assert_cycle(u1)
|
||||
s2 = Session()
|
||||
s2.add(u1)
|
||||
self._assert_cycle(u1)
|
||||
self._assert_modified(u1)
|
||||
|
||||
def test_persistent_dirty_to_expired(self):
|
||||
sess, u1 = self._persistent_fixture()
|
||||
u1.name = 'edchanged'
|
||||
self._assert_cycle(u1)
|
||||
self._assert_modified(u1)
|
||||
sess.expire(u1)
|
||||
self._assert_no_cycle(u1)
|
||||
self._assert_not_modified(u1)
|
||||
|
||||
class WeakIdentityMapTest(_fixtures.FixtureTest):
|
||||
run_inserts = None
|
||||
|
||||
@@ -1036,7 +1036,7 @@ class BaseRelationFromJoinedSubclassTest(_Polymorphic):
|
||||
sess.add_all([e1, e2])
|
||||
sess.flush()
|
||||
|
||||
def test_correct_subquery(self):
|
||||
def test_correct_subquery_nofrom(self):
|
||||
sess = create_session()
|
||||
# use Person.paperwork here just to give the least
|
||||
# amount of context
|
||||
@@ -1083,6 +1083,57 @@ class BaseRelationFromJoinedSubclassTest(_Polymorphic):
|
||||
)
|
||||
)
|
||||
|
||||
def test_correct_subquery_existingfrom(self):
|
||||
sess = create_session()
|
||||
# use Person.paperwork here just to give the least
|
||||
# amount of context
|
||||
q = sess.query(Engineer).\
|
||||
filter(Engineer.primary_language == 'java').\
|
||||
join(Engineer.paperwork).\
|
||||
filter(Paperwork.description == "tps report #2").\
|
||||
options(subqueryload(Person.paperwork))
|
||||
def go():
|
||||
eq_(q.one().paperwork,
|
||||
[Paperwork(description="tps report #1"),
|
||||
Paperwork(description="tps report #2")],
|
||||
|
||||
)
|
||||
self.assert_sql_execution(
|
||||
testing.db,
|
||||
go,
|
||||
CompiledSQL(
|
||||
"SELECT people.person_id AS people_person_id, "
|
||||
"people.name AS people_name, people.type AS people_type, "
|
||||
"engineers.engineer_id AS engineers_engineer_id, "
|
||||
"engineers.primary_language AS engineers_primary_language "
|
||||
"FROM people JOIN engineers "
|
||||
"ON people.person_id = engineers.engineer_id "
|
||||
"JOIN paperwork ON people.person_id = paperwork.person_id "
|
||||
"WHERE engineers.primary_language = :primary_language_1 "
|
||||
"AND paperwork.description = :description_1",
|
||||
{"primary_language_1": "java",
|
||||
"description_1": "tps report #2"}
|
||||
),
|
||||
CompiledSQL(
|
||||
"SELECT paperwork.paperwork_id AS paperwork_paperwork_id, "
|
||||
"paperwork.description AS paperwork_description, "
|
||||
"paperwork.person_id AS paperwork_person_id, "
|
||||
"anon_1.people_person_id AS anon_1_people_person_id "
|
||||
"FROM (SELECT people.person_id AS people_person_id "
|
||||
"FROM people JOIN engineers ON people.person_id = "
|
||||
"engineers.engineer_id JOIN paperwork "
|
||||
"ON people.person_id = paperwork.person_id "
|
||||
"WHERE engineers.primary_language = :primary_language_1 AND "
|
||||
"paperwork.description = :description_1) AS anon_1 "
|
||||
"JOIN paperwork ON anon_1.people_person_id = "
|
||||
"paperwork.person_id "
|
||||
"ORDER BY anon_1.people_person_id, paperwork.paperwork_id",
|
||||
{"primary_language_1": "java",
|
||||
"description_1": "tps report #2"}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
class SelfReferentialTest(fixtures.MappedTest):
|
||||
|
||||
Reference in New Issue
Block a user