diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-04-03 11:00:19 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-04-03 11:00:19 -0400 |
| commit | efa88d4af96e78751409e2f4a66bafe866286d8d (patch) | |
| tree | a354d27a63378f2272d9017d3dde3c8b686d7303 /lib | |
| parent | f432bc8fe15507653caff6f57e54d4bf6a78fbd7 (diff) | |
| parent | 724012541b7db981efe089f9d4fdc8b944dba267 (diff) | |
| download | sqlalchemy-efa88d4af96e78751409e2f4a66bafe866286d8d.tar.gz | |
branch merge
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/sqlalchemy/cextension/resultproxy.c | 35 | ||||
| -rw-r--r-- | lib/sqlalchemy/ext/compiler.py | 21 | ||||
| -rw-r--r-- | lib/sqlalchemy/ext/declarative.py | 8 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/__init__.py | 24 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 11 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/properties.py | 36 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/session.py | 27 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 1 | ||||
| -rw-r--r-- | lib/sqlalchemy/schema.py | 5 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/expression.py | 10 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/util.py | 3 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/visitors.py | 21 | ||||
| -rw-r--r-- | lib/sqlalchemy/test/requires.py | 12 | ||||
| -rw-r--r-- | lib/sqlalchemy/topological.py | 23 |
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()) |
