summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-04-03 11:00:19 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2010-04-03 11:00:19 -0400
commitefa88d4af96e78751409e2f4a66bafe866286d8d (patch)
treea354d27a63378f2272d9017d3dde3c8b686d7303 /lib
parentf432bc8fe15507653caff6f57e54d4bf6a78fbd7 (diff)
parent724012541b7db981efe089f9d4fdc8b944dba267 (diff)
downloadsqlalchemy-efa88d4af96e78751409e2f4a66bafe866286d8d.tar.gz
branch merge
Diffstat (limited to 'lib')
-rw-r--r--lib/sqlalchemy/cextension/resultproxy.c35
-rw-r--r--lib/sqlalchemy/ext/compiler.py21
-rw-r--r--lib/sqlalchemy/ext/declarative.py8
-rw-r--r--lib/sqlalchemy/orm/__init__.py24
-rw-r--r--lib/sqlalchemy/orm/attributes.py11
-rw-r--r--lib/sqlalchemy/orm/properties.py36
-rw-r--r--lib/sqlalchemy/orm/session.py27
-rw-r--r--lib/sqlalchemy/orm/strategies.py1
-rw-r--r--lib/sqlalchemy/schema.py5
-rw-r--r--lib/sqlalchemy/sql/expression.py10
-rw-r--r--lib/sqlalchemy/sql/util.py3
-rw-r--r--lib/sqlalchemy/sql/visitors.py21
-rw-r--r--lib/sqlalchemy/test/requires.py12
-rw-r--r--lib/sqlalchemy/topological.py23
14 files changed, 166 insertions, 71 deletions
diff --git a/lib/sqlalchemy/cextension/resultproxy.c b/lib/sqlalchemy/cextension/resultproxy.c
index b530b65f7..5d9100469 100644
--- a/lib/sqlalchemy/cextension/resultproxy.c
+++ b/lib/sqlalchemy/cextension/resultproxy.c
@@ -69,8 +69,8 @@ BaseRowProxy_init(BaseRowProxy *self, PyObject *args, PyObject *kwds)
Py_INCREF(parent);
self->parent = parent;
- if (!PyTuple_CheckExact(row)) {
- PyErr_SetString(PyExc_TypeError, "row must be a tuple");
+ if (!PySequence_Check(row)) {
+ PyErr_SetString(PyExc_TypeError, "row must be a sequence");
return -1;
}
Py_INCREF(row);
@@ -148,13 +148,15 @@ BaseRowProxy_processvalues(PyObject *values, PyObject *processors, int astuple)
{
Py_ssize_t num_values, num_processors;
PyObject **valueptr, **funcptr, **resultptr;
- PyObject *func, *result, *processed_value;
+ PyObject *func, *result, *processed_value, *values_fastseq;
num_values = Py_SIZE(values);
num_processors = Py_SIZE(processors);
if (num_values != num_processors) {
- PyErr_SetString(PyExc_RuntimeError,
- "number of values in row differ from number of column processors");
+ PyErr_Format(PyExc_RuntimeError,
+ "number of values in row (%d) differ from number of column "
+ "processors (%d)",
+ num_values, num_processors);
return NULL;
}
@@ -166,9 +168,11 @@ BaseRowProxy_processvalues(PyObject *values, PyObject *processors, int astuple)
if (result == NULL)
return NULL;
- /* we don't need to use PySequence_Fast as long as values, processors and
- * result are simple tuple or lists. */
- valueptr = PySequence_Fast_ITEMS(values);
+ values_fastseq = PySequence_Fast(values, "row must be a sequence");
+ if (values_fastseq == NULL)
+ return NULL;
+
+ valueptr = PySequence_Fast_ITEMS(values_fastseq);
funcptr = PySequence_Fast_ITEMS(processors);
resultptr = PySequence_Fast_ITEMS(result);
while (--num_values >= 0) {
@@ -177,6 +181,7 @@ BaseRowProxy_processvalues(PyObject *values, PyObject *processors, int astuple)
processed_value = PyObject_CallFunctionObjArgs(func, *valueptr,
NULL);
if (processed_value == NULL) {
+ Py_DECREF(values_fastseq);
Py_DECREF(result);
return NULL;
}
@@ -189,6 +194,7 @@ BaseRowProxy_processvalues(PyObject *values, PyObject *processors, int astuple)
funcptr++;
resultptr++;
}
+ Py_DECREF(values_fastseq);
return result;
}
@@ -199,19 +205,12 @@ BaseRowProxy_values(BaseRowProxy *self)
self->processors, 0);
}
-static PyTupleObject *
-BaseRowProxy_tuplevalues(BaseRowProxy *self)
-{
- return (PyTupleObject *)BaseRowProxy_processvalues(self->row,
- self->processors, 1);
-}
-
static PyObject *
BaseRowProxy_iter(BaseRowProxy *self)
{
PyObject *values, *result;
- values = (PyObject *)BaseRowProxy_tuplevalues(self);
+ values = BaseRowProxy_processvalues(self->row, self->processors, 1);
if (values == NULL)
return NULL;
@@ -393,9 +392,9 @@ BaseRowProxy_setrow(BaseRowProxy *self, PyObject *value, void *closure)
return -1;
}
- if (!PyTuple_CheckExact(value)) {
+ if (!PySequence_Check(value)) {
PyErr_SetString(PyExc_TypeError,
- "The 'row' attribute value must be a tuple");
+ "The 'row' attribute value must be a sequence");
return -1;
}
diff --git a/lib/sqlalchemy/ext/compiler.py b/lib/sqlalchemy/ext/compiler.py
index 3226b0efd..20d6aa05f 100644
--- a/lib/sqlalchemy/ext/compiler.py
+++ b/lib/sqlalchemy/ext/compiler.py
@@ -147,8 +147,23 @@ A big part of using the compiler extension is subclassing SQLAlchemy expression
function or stored procedure type of call. Since most databases support
statements along the line of "SELECT FROM <some function>"
``FunctionElement`` adds in the ability to be used in the FROM clause of a
- ``select()`` construct.
-
+ ``select()`` construct::
+
+ from sqlalchemy.sql.expression import FunctionElement
+
+ class coalesce(FunctionElement):
+ name = 'coalesce'
+
+ @compiles(coalesce)
+ def compile(element, compiler, **kw):
+ return "coalesce(%s)" % compiler.process(element.clauses)
+
+ @compiles(coalesce, 'oracle')
+ def compile(element, compiler, **kw):
+ if len(element.clauses) > 2:
+ raise TypeError("coalesce only supports two arguments on Oracle")
+ return "nvl(%s)" % compiler.process(element.clauses)
+
* :class:`~sqlalchemy.schema.DDLElement` - The root of all DDL expressions,
like CREATE TABLE, ALTER TABLE, etc. Compilation of ``DDLElement``
subclasses is issued by a ``DDLCompiler`` instead of a ``SQLCompiler``.
@@ -165,7 +180,7 @@ A big part of using the compiler extension is subclassing SQLAlchemy expression
def compiles(class_, *specs):
def decorate(fn):
- existing = getattr(class_, '_compiler_dispatcher', None)
+ existing = class_.__dict__.get('_compiler_dispatcher', None)
if not existing:
existing = _dispatcher()
diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative.py
index 1f4658b60..407de1004 100644
--- a/lib/sqlalchemy/ext/declarative.py
+++ b/lib/sqlalchemy/ext/declarative.py
@@ -749,8 +749,12 @@ class _GetColumns(object):
mapper = class_mapper(self.cls, compile=False)
if mapper:
- prop = mapper.get_property(key)
- if not isinstance(prop, ColumnProperty):
+ prop = mapper.get_property(key, raiseerr=False)
+ if prop is None:
+ raise exceptions.InvalidRequestError(
+ "Class %r does not have a mapped column named %r"
+ % (self.cls, key))
+ elif not isinstance(prop, ColumnProperty):
raise exceptions.InvalidRequestError(
"Property %r is not an instance of"
" ColumnProperty (i.e. does not correspond"
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py
index 206c8d0c2..c2f6337bc 100644
--- a/lib/sqlalchemy/orm/__init__.py
+++ b/lib/sqlalchemy/orm/__init__.py
@@ -266,6 +266,9 @@ def relationship(argument, secondary=None, **kwargs):
a class which extends :class:`RelationshipProperty.Comparator` which
provides custom SQL clause generation for comparison operations.
+ :param doc:
+ docstring which will be applied to the resulting descriptor.
+
:param extension:
an :class:`AttributeExtension` instance, or list of extensions,
which will be prepended to the list of attribute listeners for
@@ -469,7 +472,7 @@ def relation(*arg, **kw):
def dynamic_loader(argument, secondary=None, primaryjoin=None,
secondaryjoin=None, foreign_keys=None, backref=None,
post_update=False, cascade=False, remote_side=None,
- enable_typechecks=True, passive_deletes=False,
+ enable_typechecks=True, passive_deletes=False, doc=None,
order_by=None, comparator_factory=None, query_class=None):
"""Construct a dynamically-loading mapper property.
@@ -508,7 +511,7 @@ def dynamic_loader(argument, secondary=None, primaryjoin=None,
secondaryjoin=secondaryjoin, foreign_keys=foreign_keys, backref=backref,
post_update=post_update, cascade=cascade, remote_side=remote_side,
enable_typechecks=enable_typechecks, passive_deletes=passive_deletes,
- order_by=order_by, comparator_factory=comparator_factory,
+ order_by=order_by, comparator_factory=comparator_factory,doc=doc,
strategy_class=DynaLoader, query_class=query_class)
def column_property(*args, **kwargs):
@@ -538,7 +541,11 @@ def column_property(*args, **kwargs):
it does not load immediately, and is instead loaded when the
attribute is first accessed on an instance. See also
:func:`~sqlalchemy.orm.deferred`.
-
+
+ doc
+ optional string that will be applied as the doc on the
+ class-bound descriptor.
+
extension
an :class:`~sqlalchemy.orm.interfaces.AttributeExtension` instance,
or list of extensions, which will be prepended to the list of
@@ -612,6 +619,10 @@ def composite(class_, *cols, **kwargs):
a class which extends ``sqlalchemy.orm.properties.CompositeProperty.Comparator``
which provides custom SQL clause generation for comparison operations.
+ doc
+ optional string that will be applied as the doc on the
+ class-bound descriptor.
+
extension
an :class:`~sqlalchemy.orm.interfaces.AttributeExtension` instance,
or list of extensions, which will be prepended to the list of
@@ -813,7 +824,7 @@ def mapper(class_, local_table=None, *args, **params):
"""
return Mapper(class_, local_table, *args, **params)
-def synonym(name, map_column=False, descriptor=None, comparator_factory=None):
+def synonym(name, map_column=False, descriptor=None, comparator_factory=None, doc=None):
"""Set up `name` as a synonym to another mapped property.
Used with the ``properties`` dictionary sent to :func:`~sqlalchemy.orm.mapper`.
@@ -851,7 +862,10 @@ def synonym(name, map_column=False, descriptor=None, comparator_factory=None):
proxy access to the column-based attribute.
"""
- return SynonymProperty(name, map_column=map_column, descriptor=descriptor, comparator_factory=comparator_factory)
+ return SynonymProperty(name, map_column=map_column,
+ descriptor=descriptor,
+ comparator_factory=comparator_factory,
+ doc=doc)
def comparable_property(comparator_factory, descriptor=None):
"""Provide query semantics for an unmanaged attribute.
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index 887d9a9c1..b631ea2c9 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -1366,12 +1366,12 @@ def unregister_class(class_):
instrumentation_registry.unregister(class_)
def register_attribute(class_, key, **kw):
-
proxy_property = kw.pop('proxy_property', None)
comparator = kw.pop('comparator', None)
parententity = kw.pop('parententity', None)
- register_descriptor(class_, key, proxy_property, comparator, parententity)
+ doc = kw.pop('doc', None)
+ register_descriptor(class_, key, proxy_property, comparator, parententity, doc=doc)
if not proxy_property:
register_attribute_impl(class_, key, **kw)
@@ -1405,7 +1405,8 @@ def register_attribute_impl(class_, key,
manager.post_configure_attribute(key)
-def register_descriptor(class_, key, proxy_property=None, comparator=None, parententity=None, property_=None):
+def register_descriptor(class_, key, proxy_property=None, comparator=None,
+ parententity=None, property_=None, doc=None):
manager = manager_of_class(class_)
if proxy_property:
@@ -1413,7 +1414,9 @@ def register_descriptor(class_, key, proxy_property=None, comparator=None, paren
descriptor = proxy_type(key, proxy_property, comparator, parententity)
else:
descriptor = InstrumentedAttribute(key, comparator=comparator, parententity=parententity)
-
+
+ descriptor.__doc__ = doc
+
manager.instrument_attribute(key, descriptor)
def unregister_attribute(class_, key):
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py
index a8295e2cd..41024101b 100644
--- a/lib/sqlalchemy/orm/properties.py
+++ b/lib/sqlalchemy/orm/properties.py
@@ -58,6 +58,8 @@ class ColumnProperty(StrategizedProperty):
self.comparator_factory = kwargs.pop('comparator_factory', self.__class__.Comparator)
self.descriptor = kwargs.pop('descriptor', None)
self.extension = kwargs.pop('extension', None)
+ self.doc = kwargs.pop('doc', getattr(columns[0], 'doc', None))
+
if kwargs:
raise TypeError(
"%s received unexpected keyword argument(s): %s" % (
@@ -80,7 +82,8 @@ class ColumnProperty(StrategizedProperty):
self.key,
comparator=self.comparator_factory(self, mapper),
parententity=mapper,
- property_=self
+ property_=self,
+ doc=self.doc
)
def do_init(self):
@@ -259,11 +262,12 @@ class SynonymProperty(MapperProperty):
extension = None
- def __init__(self, name, map_column=None, descriptor=None, comparator_factory=None):
+ def __init__(self, name, map_column=None, descriptor=None, comparator_factory=None, doc=None):
self.name = name
self.map_column = map_column
self.descriptor = descriptor
self.comparator_factory = comparator_factory
+ self.doc = doc or (descriptor and descriptor.__doc__) or None
util.set_creation_order(self)
def setup(self, context, entity, path, adapter, **kwargs):
@@ -303,7 +307,8 @@ class SynonymProperty(MapperProperty):
comparator=comparator_callable(self, mapper),
parententity=mapper,
property_=self,
- proxy_property=self.descriptor
+ proxy_property=self.descriptor,
+ doc=self.doc
)
def merge(self, session, source_state, source_dict, dest_state, dest_dict, load, _recursive):
@@ -316,9 +321,10 @@ class ComparableProperty(MapperProperty):
extension = None
- def __init__(self, comparator_factory, descriptor=None):
+ def __init__(self, comparator_factory, descriptor=None, doc=None):
self.descriptor = descriptor
self.comparator_factory = comparator_factory
+ self.doc = doc or (descriptor and descriptor.__doc__) or None
util.set_creation_order(self)
def instrument_class(self, mapper):
@@ -330,7 +336,8 @@ class ComparableProperty(MapperProperty):
comparator=self.comparator_factory(self, mapper),
parententity=mapper,
property_=self,
- proxy_property=self.descriptor
+ proxy_property=self.descriptor,
+ doc=self.doc,
)
def setup(self, context, entity, path, adapter, **kwargs):
@@ -364,6 +371,7 @@ class RelationshipProperty(StrategizedProperty):
enable_typechecks=True, join_depth=None,
comparator_factory=None,
single_parent=False, innerjoin=False,
+ doc=None,
strategy_class=None, _local_remote_pairs=None, query_class=None):
self.uselist = uselist
@@ -384,7 +392,7 @@ class RelationshipProperty(StrategizedProperty):
self.enable_typechecks = enable_typechecks
self.query_class = query_class
self.innerjoin = innerjoin
-
+ self.doc = doc
self.join_depth = join_depth
self.local_remote_pairs = _local_remote_pairs
self.extension = extension
@@ -433,7 +441,8 @@ class RelationshipProperty(StrategizedProperty):
self.key,
comparator=self.comparator_factory(self, mapper),
parententity=mapper,
- property_=self
+ property_=self,
+ doc=self.doc,
)
class Comparator(PropComparator):
@@ -742,6 +751,8 @@ class RelationshipProperty(StrategizedProperty):
else:
instances = state.value_as_iterable(self.key, passive=passive)
+ skip_pending = type_ == 'refresh-expire' and 'delete-orphan' not in self.cascade
+
if instances:
for c in instances:
if c is not None and \
@@ -757,12 +768,17 @@ class RelationshipProperty(StrategizedProperty):
str(self.parent.class_),
str(c.__class__)
))
+ instance_state = attributes.instance_state(c)
+
+ if skip_pending and not instance_state.key:
+ continue
+
visited_instances.add(c)
# cascade using the mapper local to this
# object, so that its individual properties are located
- instance_mapper = object_mapper(c)
- yield (c, instance_mapper, attributes.instance_state(c))
+ instance_mapper = instance_state.manager.mapper
+ yield (c, instance_mapper, instance_state)
def _add_reverse_property(self, key):
other = self.mapper._get_property(key)
@@ -1142,7 +1158,7 @@ class RelationshipProperty(StrategizedProperty):
parent = self.parent.primary_mapper()
kwargs.setdefault('viewonly', self.viewonly)
kwargs.setdefault('post_update', self.post_update)
-
+
self.back_populates = backref_key
relationship = RelationshipProperty(
parent,
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index 0a3fbe79e..0810175bf 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -883,7 +883,7 @@ class Session(object):
state.commit_all(dict_, self.identity_map)
def refresh(self, instance, attribute_names=None, lockmode=None):
- """Refresh the attributes on the given instance.
+ """Expire and refresh the attributes on the given instance.
A query will be issued to the database and all attributes will be
refreshed with their current database value.
@@ -907,7 +907,9 @@ class Session(object):
state = attributes.instance_state(instance)
except exc.NO_STATE:
raise exc.UnmappedInstanceError(instance)
- self._validate_persistent(state)
+
+ self._expire_state(state, attribute_names)
+
if self.query(_object_mapper(instance))._get(
state.key, refresh_state=state,
lockmode=lockmode,
@@ -939,18 +941,31 @@ class Session(object):
state = attributes.instance_state(instance)
except exc.NO_STATE:
raise exc.UnmappedInstanceError(instance)
+ self._expire_state(state, attribute_names)
+
+ def _expire_state(self, state, attribute_names):
self._validate_persistent(state)
if attribute_names:
_expire_state(state, state.dict,
- attribute_names=attribute_names, instance_dict=self.identity_map)
+ attribute_names=attribute_names,
+ instance_dict=self.identity_map)
else:
# pre-fetch the full cascade since the expire is going to
# remove associations
cascaded = list(_cascade_state_iterator('refresh-expire', state))
- _expire_state(state, state.dict, None, instance_dict=self.identity_map)
+ self._conditional_expire(state)
for (state, m, o) in cascaded:
- _expire_state(state, state.dict, None, instance_dict=self.identity_map)
-
+ self._conditional_expire(state)
+
+ def _conditional_expire(self, state):
+ """Expire a state if persistent, else expunge if pending"""
+
+ if state.key:
+ _expire_state(state, state.dict, None, instance_dict=self.identity_map)
+ elif state in self._new:
+ self._new.pop(state)
+ state.detach()
+
def prune(self):
"""Remove unreferenced instances cached in the identity map.
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 93b1170f4..39657564a 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -66,6 +66,7 @@ def _register_attribute(strategy, mapper, useobject,
callable_=callable_,
active_history=active_history,
impl_class=impl_class,
+ doc=prop.doc,
**kw
)
diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py
index 8ffb68a4e..0e03be686 100644
--- a/lib/sqlalchemy/schema.py
+++ b/lib/sqlalchemy/schema.py
@@ -535,6 +535,10 @@ class Column(SchemaItem, expression.ColumnClause):
Contrast this argument to ``server_default`` which creates a
default generator on the database side.
+ :param doc: optional String that can be used by the ORM or similar
+ to document attributes. This attribute does not render SQL
+ comments (a future attribute 'comment' will achieve that).
+
:param key: An optional string identifier which will identify this
``Column`` object on the :class:`Table`. When a key is provided,
this is the only identifier referencing the ``Column`` within the
@@ -651,6 +655,7 @@ class Column(SchemaItem, expression.ColumnClause):
self.index = kwargs.pop('index', None)
self.unique = kwargs.pop('unique', None)
self.quote = kwargs.pop('quote', None)
+ self.doc = kwargs.pop('doc', None)
self.onupdate = kwargs.pop('onupdate', None)
self.autoincrement = kwargs.pop('autoincrement', True)
self.constraints = set()
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index 3aaa06fd6..5958a0bc4 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -3185,6 +3185,16 @@ class ColumnClause(_Immutable, ColumnElement):
label = _escape_for_generated(self.table.name) + "_" + \
_escape_for_generated(self.name)
+ # ensure the label name doesn't conflict with that
+ # of an existing column
+ if label in self.table.c:
+ _label = label
+ counter = 1
+ while _label in self.table.c:
+ _label = label + "_" + str(counter)
+ counter += 1
+ label = _label
+
return _generated_label(label)
else:
diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py
index d5575e0e7..5a439b099 100644
--- a/lib/sqlalchemy/sql/util.py
+++ b/lib/sqlalchemy/sql/util.py
@@ -15,7 +15,8 @@ def sort_tables(tables):
parent_table = fkey.column.table
if parent_table in tables:
child_table = fkey.parent.table
- tuples.append( ( parent_table, child_table ) )
+ if parent_table is not child_table:
+ tuples.append((parent_table, child_table))
for table in tables:
visitors.traverse(table, {'schema_visitor':True}, {'foreign_key':visit_foreign_key})
diff --git a/lib/sqlalchemy/sql/visitors.py b/lib/sqlalchemy/sql/visitors.py
index 4a54375f8..799486c02 100644
--- a/lib/sqlalchemy/sql/visitors.py
+++ b/lib/sqlalchemy/sql/visitors.py
@@ -40,16 +40,17 @@ class VisitableType(type):
# set up an optimized visit dispatch function
# for use by the compiler
- visit_name = cls.__visit_name__
- if isinstance(visit_name, str):
- getter = operator.attrgetter("visit_%s" % visit_name)
- def _compiler_dispatch(self, visitor, **kw):
- return getter(visitor)(self, **kw)
- else:
- def _compiler_dispatch(self, visitor, **kw):
- return getattr(visitor, 'visit_%s' % self.__visit_name__)(self, **kw)
-
- cls._compiler_dispatch = _compiler_dispatch
+ if '__visit_name__' in cls.__dict__:
+ visit_name = cls.__visit_name__
+ if isinstance(visit_name, str):
+ getter = operator.attrgetter("visit_%s" % visit_name)
+ def _compiler_dispatch(self, visitor, **kw):
+ return getter(visitor)(self, **kw)
+ else:
+ def _compiler_dispatch(self, visitor, **kw):
+ return getattr(visitor, 'visit_%s' % self.__visit_name__)(self, **kw)
+
+ cls._compiler_dispatch = _compiler_dispatch
super(VisitableType, cls).__init__(clsname, bases, clsdict)
diff --git a/lib/sqlalchemy/test/requires.py b/lib/sqlalchemy/test/requires.py
index 73b212095..bf911c2c2 100644
--- a/lib/sqlalchemy/test/requires.py
+++ b/lib/sqlalchemy/test/requires.py
@@ -149,6 +149,18 @@ def sequences(fn):
no_support('sybase', 'no SEQUENCE support'),
)
+def update_nowait(fn):
+ """Target database must support SELECT...FOR UPDATE NOWAIT"""
+ return _chain_decorators_on(
+ fn,
+ no_support('access', 'no FOR UPDATE NOWAIT support'),
+ no_support('firebird', 'no FOR UPDATE NOWAIT support'),
+ no_support('mssql', 'no FOR UPDATE NOWAIT support'),
+ no_support('mysql', 'no FOR UPDATE NOWAIT support'),
+ no_support('sqlite', 'no FOR UPDATE NOWAIT support'),
+ no_support('sybase', 'no FOR UPDATE NOWAIT support'),
+ )
+
def subqueries(fn):
"""Target database must support subqueries."""
return _chain_decorators_on(
diff --git a/lib/sqlalchemy/topological.py b/lib/sqlalchemy/topological.py
index d35213f6b..d061aec04 100644
--- a/lib/sqlalchemy/topological.py
+++ b/lib/sqlalchemy/topological.py
@@ -161,21 +161,20 @@ def _sort(tuples, allitems, allow_cycles=False, ignore_self_cycles=False):
edges = _EdgeCollection()
for item in list(allitems) + [t[0] for t in tuples] + [t[1] for t in tuples]:
- item_id = id(item)
- if item_id not in nodes:
- nodes[item_id] = _Node(item)
+ if item not in nodes:
+ nodes[item] = _Node(item)
for t in tuples:
- id0, id1 = id(t[0]), id(t[1])
- if t[0] is t[1]:
+ t0, t1 = t[0], t[1]
+ if t0 is t1:
if allow_cycles:
- n = nodes[id0]
+ n = nodes[t0]
n.cycles = set([n])
elif not ignore_self_cycles:
raise CircularDependencyError("Self-referential dependency detected: %r" % t)
continue
- childnode = nodes[id1]
- parentnode = nodes[id0]
+ childnode = nodes[t1]
+ parentnode = nodes[t0]
edges.add((parentnode, childnode))
queue = []
@@ -207,11 +206,11 @@ def _sort(tuples, allitems, allow_cycles=False, ignore_self_cycles=False):
continue
else:
# long cycles not allowed
- raise CircularDependencyError("Circular dependency detected: %r %r " % (edges, queue))
+ raise CircularDependencyError("Circular dependency detected: %r" % edges)
node = queue.pop()
if not hasattr(node, '_cyclical'):
output.append(node)
- del nodes[id(node.item)]
+ del nodes[node.item]
for childnode in edges.pop_node(node):
queue.append(childnode)
return output
@@ -270,7 +269,7 @@ def _find_cycles(edges):
for (n, key) in edges.edges_by_parent(node):
if key in cycle:
continue
- cycle.add(key)
+ cycle.append(key)
if key is goal:
cycset = set(cycle)
for x in cycle:
@@ -287,7 +286,7 @@ def _find_cycles(edges):
cycle.pop()
for parent in edges.get_parents():
- traverse(parent, set(), parent)
+ traverse(parent, [], parent)
unique_cycles = set(tuple(s) for s in cycles.values())