This file is indexed.

/usr/share/pyshared/elixir/ext/associable.py is in python-elixir 0.7.1-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
'''
Associable Elixir Statement Generator

==========
Associable
==========

About Polymorphic Associations
------------------------------

A frequent pattern in database schemas is the has_and_belongs_to_many, or a
many-to-many table. Quite often multiple tables will refer to a single one
creating quite a few many-to-many intermediate tables.

Polymorphic associations lower the amount of many-to-many tables by setting up
a table that allows relations to any other table in the database, and relates
it to the associable table. In some implementations, this layout does not
enforce referential integrity with database foreign key constraints, this
implementation uses an additional many-to-many table with foreign key
constraints to avoid this problem.

.. note:
    SQLite does not support foreign key constraints, so referential integrity
    can only be enforced using database backends with such support.

Elixir Statement Generator for Polymorphic Associations
-------------------------------------------------------

The ``associable`` function generates the intermediary tables for an Elixir
entity that should be associable with other Elixir entities and returns an
Elixir Statement for use with them. This automates the process of creating the
polymorphic association tables and ensuring their referential integrity.

Matching select_XXX and select_by_XXX are also added to the associated entity
which allow queries to be run for the associated objects.

Example usage:

.. sourcecode:: python

    class Tag(Entity):
        name = Field(Unicode)

    acts_as_taggable = associable(Tag)

    class Entry(Entity):
        title = Field(Unicode)
        acts_as_taggable('tags')

    class Article(Entity):
        title = Field(Unicode)
        acts_as_taggable('tags')

Or if one of the entities being associated should only have a single member of
the associated table:

.. sourcecode:: python

    class Address(Entity):
        street = Field(String(130))
        city = Field(String(100))

    is_addressable = associable(Address, 'addresses')

    class Person(Entity):
        name = Field(Unicode)
        orders = OneToMany('Order')
        is_addressable()

    class Order(Entity):
        order_num = Field(primary_key=True)
        item_count = Field(Integer)
        person = ManyToOne('Person')
        is_addressable('address', uselist=False)

    home = Address(street='123 Elm St.', city='Spooksville')
    user = Person(name='Jane Doe')
    user.addresses.append(home)

    neworder = Order(item_count=4)
    neworder.address = home
    user.orders.append(neworder)

    # Queries using the added helpers
    Person.select_by_addresses(city='Cupertino')
    Person.select_addresses(and_(Address.c.street=='132 Elm St',
                                 Address.c.city=='Smallville'))

Statement Options
-----------------

The generated Elixir Statement has several options available:

+---------------+-------------------------------------------------------------+
| Option Name   | Description                                                 |
+===============+=============================================================+
| ``name``      | Specify a custom name for the Entity attribute. This is     |
|               | used to declare the attribute used to access the associated |
|               | table values. Otherwise, the name will use the plural_name  |
|               | provided to the associable call.                            |
+---------------+-------------------------------------------------------------+
| ``uselist``   | Whether or not the associated table should be represented   |
|               | as a list, or a single property. It should be set to False  |
|               | when the entity should only have a single associated        |
|               | entity. Defaults to True.                                   |
+---------------+-------------------------------------------------------------+
| ``lazy``      | Determines eager loading of the associated entity objects.  |
|               | Defaults to False, to indicate that they should not be      |
|               | lazily loaded.                                              |
+---------------+-------------------------------------------------------------+
'''
from elixir.statements import Statement
import sqlalchemy as sa

__doc_all__ = ['associable']


def associable(assoc_entity, plural_name=None, lazy=True):
    '''
    Generate an associable Elixir Statement
    '''
    interface_name = assoc_entity._descriptor.tablename
    able_name = interface_name + 'able'

    if plural_name:
        attr_name = "%s_rel" % plural_name
    else:
        plural_name = interface_name
        attr_name = "%s_rel" % interface_name

    class GenericAssoc(object):

        def __init__(self, tablename):
            self.type = tablename

    #TODO: inherit from entity builder
    class Associable(object):
        """An associable Elixir Statement object"""

        def __init__(self, entity, name=None, uselist=True, lazy=True):
            self.entity = entity
            self.lazy = lazy
            self.uselist = uselist

            if name is None:
                self.name = plural_name
            else:
                self.name = name

        def after_table(self):
            col = sa.Column('%s_assoc_id' % interface_name, sa.Integer,
                            sa.ForeignKey('%s.id' % able_name))
            self.entity._descriptor.add_column(col)

            if not hasattr(assoc_entity, '_assoc_table'):
                metadata = assoc_entity._descriptor.metadata
                association_table = sa.Table("%s" % able_name, metadata,
                    sa.Column('id', sa.Integer, primary_key=True),
                    sa.Column('type', sa.String(40), nullable=False),
                )
                tablename =  "%s_to_%s" % (able_name, interface_name)
                association_to_table = sa.Table(tablename, metadata,
                    sa.Column('assoc_id', sa.Integer,
                              sa.ForeignKey(association_table.c.id,
                                            ondelete="CASCADE"),
                              primary_key=True),
                    #FIXME: this assumes a single id col
                    sa.Column('%s_id' % interface_name, sa.Integer,
                              sa.ForeignKey(assoc_entity.table.c.id,
                                            ondelete="RESTRICT"),
                              primary_key=True),
                )

                assoc_entity._assoc_table = association_table
                assoc_entity._assoc_to_table = association_to_table

        def after_mapper(self):
            if not hasattr(assoc_entity, '_assoc_mapper'):
                assoc_entity._assoc_mapper = sa.orm.mapper(
                    GenericAssoc, assoc_entity._assoc_table, properties={
                        'targets': sa.orm.relation(
                                       assoc_entity,
                                       secondary=assoc_entity._assoc_to_table,
                                       lazy=lazy, backref='associations',
                                       order_by=assoc_entity.mapper.order_by)
                })

            entity = self.entity
            entity.mapper.add_property(
                attr_name,
                sa.orm.relation(GenericAssoc, lazy=self.lazy,
                                backref='_backref_%s' % entity.table.name)
            )

            if self.uselist:
                def get(self):
                    if getattr(self, attr_name) is None:
                        setattr(self, attr_name,
                                GenericAssoc(entity.table.name))
                    return getattr(self, attr_name).targets
                setattr(entity, self.name, property(get))
            else:
                # scalar based property decorator
                def get(self):
                    attr = getattr(self, attr_name)
                    if attr is not None:
                        return attr.targets[0]
                    else:
                        return None
                def set(self, value):
                    if getattr(self, attr_name) is None:
                        setattr(self, attr_name,
                                GenericAssoc(entity.table.name))
                    getattr(self, attr_name).targets = [value]
                setattr(entity, self.name, property(get, set))

            # self.name is both set via mapper synonym and the python
            # property, but that's how synonym properties work.
            # adding synonym property after "real" property otherwise it
            # breaks when using SQLAlchemy > 0.4.1
            entity.mapper.add_property(self.name, sa.orm.synonym(attr_name))

            # add helper methods
            def select_by(cls, **kwargs):
                return cls.query.join([attr_name, 'targets']) \
                                .filter_by(**kwargs).all()
            setattr(entity, 'select_by_%s' % self.name, classmethod(select_by))

            def select(cls, *args, **kwargs):
                return cls.query.join([attr_name, 'targets']) \
                                .filter(*args, **kwargs).all()
            setattr(entity, 'select_%s' % self.name, classmethod(select))

    return Statement(Associable)