root/galaxy-central/eggs/SQLAlchemy-0.5.6_dev_r6498-py2.6.egg/sqlalchemy/ext/orderinglist.py @ 3

リビジョン 3, 10.3 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1"""A custom list that manages index/position information for its children.
2
3``orderinglist`` is a custom list collection implementation for mapped
4relations that keeps an arbitrary "position" attribute on contained objects in
5sync with each object's position in the Python list.
6
7The collection acts just like a normal Python ``list``, with the added
8behavior that as you manipulate the list (via ``insert``, ``pop``, assignment,
9deletion, what have you), each of the objects it contains is updated as needed
10to reflect its position.  This is very useful for managing ordered relations
11which have a user-defined, serialized order::
12
13  >>> from sqlalchemy import MetaData, Table, Column, Integer, String, ForeignKey
14  >>> from sqlalchemy.orm import mapper, relation
15  >>> from sqlalchemy.ext.orderinglist import ordering_list
16
17A simple model of users their "top 10" things::
18
19  >>> metadata = MetaData()
20  >>> users = Table('users', metadata,
21  ...               Column('id', Integer, primary_key=True))
22  >>> blurbs = Table('user_top_ten_list', metadata,
23  ...               Column('id', Integer, primary_key=True),
24  ...               Column('user_id', Integer, ForeignKey('users.id')),
25  ...               Column('position', Integer),
26  ...               Column('blurb', String(80)))
27  >>> class User(object):
28  ...   pass
29  ...
30  >>> class Blurb(object):
31  ...    def __init__(self, blurb):
32  ...        self.blurb = blurb
33  ...
34  >>> mapper(User, users, properties={
35  ...  'topten': relation(Blurb, collection_class=ordering_list('position'),
36  ...                     order_by=[blurbs.c.position])})
37  <Mapper ...>
38  >>> mapper(Blurb, blurbs)
39  <Mapper ...>
40
41Acts just like a regular list::
42
43  >>> u = User()
44  >>> u.topten.append(Blurb('Number one!'))
45  >>> u.topten.append(Blurb('Number two!'))
46
47But the ``.position`` attibute is set automatically behind the scenes::
48
49  >>> assert [blurb.position for blurb in u.topten] == [0, 1]
50
51The objects will be renumbered automaticaly after any list-changing operation,
52for example an ``insert()``::
53
54  >>> u.topten.insert(1, Blurb('I am the new Number Two.'))
55  >>> assert [blurb.position for blurb in u.topten] == [0, 1, 2]
56  >>> assert u.topten[1].blurb == 'I am the new Number Two.'
57  >>> assert u.topten[1].position == 1
58
59Numbering and serialization are both highly configurable.  See the docstrings
60in this module and the main SQLAlchemy documentation for more information and
61examples.
62
63The :class:`~sqlalchemy.ext.orderinglist.ordering_list` factory function is the
64ORM-compatible constructor for `OrderingList` instances.
65
66"""
67from sqlalchemy.orm.collections import collection
68from sqlalchemy import util
69
70__all__ = [ 'ordering_list' ]
71
72
73def ordering_list(attr, count_from=None, **kw):
74    """Prepares an OrderingList factory for use in mapper definitions.
75
76    Returns an object suitable for use as an argument to a Mapper relation's
77    ``collection_class`` option.  Arguments are:
78
79    attr
80      Name of the mapped attribute to use for storage and retrieval of
81      ordering information
82
83    count_from (optional)
84      Set up an integer-based ordering, starting at ``count_from``.  For
85      example, ``ordering_list('pos', count_from=1)`` would create a 1-based
86      list in SQL, storing the value in the 'pos' column.  Ignored if
87      ``ordering_func`` is supplied.
88
89    Passes along any keyword arguments to ``OrderingList`` constructor.
90    """
91
92    kw = _unsugar_count_from(count_from=count_from, **kw)
93    return lambda: OrderingList(attr, **kw)
94
95# Ordering utility functions
96def count_from_0(index, collection):
97    """Numbering function: consecutive integers starting at 0."""
98
99    return index
100
101def count_from_1(index, collection):
102    """Numbering function: consecutive integers starting at 1."""
103
104    return index + 1
105
106def count_from_n_factory(start):
107    """Numbering function: consecutive integers starting at arbitrary start."""
108
109    def f(index, collection):
110        return index + start
111    try:
112        f.__name__ = 'count_from_%i' % start
113    except TypeError:
114        pass
115    return f
116
117def _unsugar_count_from(**kw):
118    """Builds counting functions from keywrod arguments.
119
120    Keyword argument filter, prepares a simple ``ordering_func`` from a
121    ``count_from`` argument, otherwise passes ``ordering_func`` on unchanged.
122    """
123
124    count_from = kw.pop('count_from', None)
125    if kw.get('ordering_func', None) is None and count_from is not None:
126        if count_from == 0:
127            kw['ordering_func'] = count_from_0
128        elif count_from == 1:
129            kw['ordering_func'] = count_from_1
130        else:
131            kw['ordering_func'] = count_from_n_factory(count_from)
132    return kw
133
134class OrderingList(list):
135    """A custom list that manages position information for its children.
136
137    See the module and __init__ documentation for more details.  The
138    ``ordering_list`` factory function is used to configure ``OrderingList``
139    collections in ``mapper`` relation definitions.
140
141    """
142
143    def __init__(self, ordering_attr=None, ordering_func=None,
144                 reorder_on_append=False):
145        """A custom list that manages position information for its children.
146
147        ``OrderingList`` is a ``collection_class`` list implementation that
148        syncs position in a Python list with a position attribute on the
149        mapped objects.
150
151        This implementation relies on the list starting in the proper order,
152        so be **sure** to put an ``order_by`` on your relation.
153
154        ordering_attr
155          Name of the attribute that stores the object's order in the
156          relation.
157
158        ordering_func
159          Optional.  A function that maps the position in the Python list to a
160          value to store in the ``ordering_attr``.  Values returned are
161          usually (but need not be!) integers.
162
163          An ``ordering_func`` is called with two positional parameters: the
164          index of the element in the list, and the list itself.
165
166          If omitted, Python list indexes are used for the attribute values.
167          Two basic pre-built numbering functions are provided in this module:
168          ``count_from_0`` and ``count_from_1``.  For more exotic examples
169          like stepped numbering, alphabetical and Fibonacci numbering, see
170          the unit tests.
171
172        reorder_on_append
173          Default False.  When appending an object with an existing (non-None)
174          ordering value, that value will be left untouched unless
175          ``reorder_on_append`` is true.  This is an optimization to avoid a
176          variety of dangerous unexpected database writes.
177
178          SQLAlchemy will add instances to the list via append() when your
179          object loads.  If for some reason the result set from the database
180          skips a step in the ordering (say, row '1' is missing but you get
181          '2', '3', and '4'), reorder_on_append=True would immediately
182          renumber the items to '1', '2', '3'.  If you have multiple sessions
183          making changes, any of whom happen to load this collection even in
184          passing, all of the sessions would try to "clean up" the numbering
185          in their commits, possibly causing all but one to fail with a
186          concurrent modification error.  Spooky action at a distance.
187
188          Recommend leaving this with the default of False, and just call
189          ``reorder()`` if you're doing ``append()`` operations with
190          previously ordered instances or when doing some housekeeping after
191          manual sql operations.
192
193        """
194        self.ordering_attr = ordering_attr
195        if ordering_func is None:
196            ordering_func = count_from_0
197        self.ordering_func = ordering_func
198        self.reorder_on_append = reorder_on_append
199
200    # More complex serialization schemes (multi column, e.g.) are possible by
201    # subclassing and reimplementing these two methods.
202    def _get_order_value(self, entity):
203        return getattr(entity, self.ordering_attr)
204
205    def _set_order_value(self, entity, value):
206        setattr(entity, self.ordering_attr, value)
207
208    def reorder(self):
209        """Synchronize ordering for the entire collection.
210
211        Sweeps through the list and ensures that each object has accurate
212        ordering information set.
213
214        """
215        for index, entity in enumerate(self):
216            self._order_entity(index, entity, True)
217
218    # As of 0.5, _reorder is no longer semi-private
219    _reorder = reorder
220
221    def _order_entity(self, index, entity, reorder=True):
222        have = self._get_order_value(entity)
223
224        # Don't disturb existing ordering if reorder is False
225        if have is not None and not reorder:
226            return
227
228        should_be = self.ordering_func(index, self)
229        if have != should_be:
230            self._set_order_value(entity, should_be)
231
232    def append(self, entity):
233        super(OrderingList, self).append(entity)
234        self._order_entity(len(self) - 1, entity, self.reorder_on_append)
235
236    def _raw_append(self, entity):
237        """Append without any ordering behavior."""
238
239        super(OrderingList, self).append(entity)
240    _raw_append = collection.adds(1)(_raw_append)
241
242    def insert(self, index, entity):
243        self[index:index] = [entity]
244
245    def remove(self, entity):
246        super(OrderingList, self).remove(entity)
247        self._reorder()
248
249    def pop(self, index=-1):
250        entity = super(OrderingList, self).pop(index)
251        self._reorder()
252        return entity
253
254    def __setitem__(self, index, entity):
255        if isinstance(index, slice):
256            for i in range(index.start or 0, index.stop or 0, index.step or 1):
257                self.__setitem__(i, entity[i])
258        else:
259            self._order_entity(index, entity, True)
260            super(OrderingList, self).__setitem__(index, entity)
261
262    def __delitem__(self, index):
263        super(OrderingList, self).__delitem__(index)
264        self._reorder()
265
266    def __setslice__(self, start, end, values):
267        super(OrderingList, self).__setslice__(start, end, values)
268        self._reorder()
269
270    def __delslice__(self, start, end):
271        super(OrderingList, self).__delslice__(start, end)
272        self._reorder()
273
274    for func_name, func in locals().items():
275        if (util.callable(func) and func.func_name == func_name and
276            not func.__doc__ and hasattr(list, func_name)):
277            func.__doc__ = getattr(list, func_name).__doc__
278    del func_name, func
279
280if __name__ == '__main__':
281    import doctest
282    doctest.testmod(optionflags=doctest.ELLIPSIS)
283
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。