~divmod-dev/divmod.org/quote-internal-schema-2770

« back to all changes in this revision

Viewing changes to Axiom/axiom/attributes.py

  • Committer: glyph
  • Date: 2005-07-28 22:09:16 UTC
  • Revision ID: svn-v4:866e43f7-fbfc-0310-8f2a-ec88d1da2979:trunk:2
move this repository to a more official-looking URL

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: axiom.test -*-
 
2
 
 
3
import os
 
4
 
 
5
from twisted.python.filepath import FilePath
 
6
 
 
7
from axiom.slotmachine import Attribute as inmemory
 
8
from axiom.extime import Time
 
9
 
 
10
_NEEDS_FETCH = object()         # token indicating that a value was not found
 
11
 
 
12
class SQLAttribute(inmemory):
 
13
    """
 
14
    Abstract superclass of all attributes.
 
15
 
 
16
    _Not_ an attribute itself.
 
17
    """
 
18
    def coercer(self, value):
 
19
        """
 
20
        must return a value equivalent to the data being passed in for it to be
 
21
        considered valid for a value of this attribute.  for example, 'int' or
 
22
        'str'.
 
23
        """
 
24
 
 
25
        raise NotImplementedError()
 
26
 
 
27
    sqltype = None
 
28
 
 
29
    def __init__(self, doc='', indexed=False):
 
30
        inmemory.__init__(self, doc)
 
31
        self.indexed = indexed
 
32
 
 
33
 
 
34
    def infilter(self, pyval, oself):
 
35
        """
 
36
        used to convert a Python value to something that lives in the database;
 
37
        so called because it is called when objects go in to the database.  It
 
38
        takes a Python value and returns an SQL value.
 
39
        """
 
40
        raise NotImplementedError()
 
41
 
 
42
    def outfilter(self, dbval, oself):
 
43
        """
 
44
        used to convert an SQL value to something that lives in memory; so
 
45
        called because it is called when objects come out of the database.  It
 
46
        takes an SQL value and returns a Python value.
 
47
        """
 
48
        return dbval
 
49
 
 
50
    # requiredSlots must be called before it's run
 
51
 
 
52
    prefix = "_atop_memory_"
 
53
    dbprefix = "_atop_store_"
 
54
 
 
55
    def requiredSlots(self, modname, classname, attrname):
 
56
        self.modname = modname
 
57
        self.classname = classname
 
58
        self.attrname = attrname
 
59
        self.columnName = attrname
 
60
        self.underlying = self.prefix + attrname
 
61
        self.dbunderlying = self.dbprefix + attrname
 
62
        yield self.underlying
 
63
        yield self.dbunderlying
 
64
 
 
65
    def fullyQualifiedName(self):
 
66
        return '.'.join([self.modname,
 
67
                         self.classname,
 
68
                         self.attrname])
 
69
 
 
70
    def __get__(self, oself, type=None):
 
71
        if type is not None and oself is None:
 
72
            return ColumnComparer(self, type)
 
73
 
 
74
        pyval = getattr(oself, self.underlying, _NEEDS_FETCH)
 
75
        st = getattr(oself, 'store')
 
76
        if pyval is _NEEDS_FETCH:
 
77
            dbval = getattr(oself, self.dbunderlying, _NEEDS_FETCH)
 
78
            if dbval is _NEEDS_FETCH:
 
79
                raise AttributeError(self.attrname)
 
80
            # cache python value
 
81
            pyval = self.outfilter(dbval, oself)
 
82
            setattr(oself, self.underlying, pyval)
 
83
        return pyval
 
84
 
 
85
    def loaded(self, oself, dbval):
 
86
        setattr(oself, self.dbunderlying, dbval)
 
87
        delattr(oself, self.underlying) # member_descriptors don't raise
 
88
                                        # attribute errors; what gives?  good
 
89
                                        # for us, I guess.
 
90
 
 
91
    def __set__(self, oself, pyval):
 
92
        # convert to dbval later, I guess?
 
93
        st = oself.store
 
94
        dbval = self.infilter(pyval, oself)
 
95
        oself.__dirty__[self.attrname] = dbval
 
96
        oself.touch()
 
97
        setattr(oself, self.underlying, pyval)
 
98
        if st is not None and st.autocommit:
 
99
            oself.checkpoint()
 
100
 
 
101
class ColumnComparer:
 
102
    def __init__(self, atr, typ):
 
103
        self.attribute = atr
 
104
        self.type = typ
 
105
        self.compared = False   # not yet activated
 
106
        self.otherComp = None
 
107
 
 
108
    def compare(self, other, sqlop):
 
109
        assert not self.compared # activation time
 
110
        self.compared = True
 
111
        # interim: maybe we want objects later?  right now strings should be fine
 
112
        if isinstance(other, ColumnComparer):
 
113
            self.otherComp = other
 
114
            self.sql = ('(%s.%s %s %s.%s)' % (self.type.getTableName(),
 
115
                                            self.attribute.columnName,
 
116
                                            sqlop,
 
117
                                            other.type.getTableName(),
 
118
                                            other.attribute.columnName))
 
119
            self.preArgs = []
 
120
        else:
 
121
            # convert to constant usable in the database
 
122
            self.sql = ('(%s.%s %s ?)' % (self.type.getTableName(),
 
123
                                        self.attribute.columnName,
 
124
                                        sqlop))
 
125
            self.preArgs = [other]
 
126
            sql = '?'
 
127
        return self
 
128
 
 
129
    def __eq__(self, other):
 
130
        return self.compare(other, '=')
 
131
    def __ne__(self, other):
 
132
        return self.compare(other, '!=')
 
133
    def __gt__(self, other):
 
134
        return self.compare(other, '>')
 
135
    def __lt__(self, other):
 
136
        return self.compare(other, '<')
 
137
    def __ge__(self, other):
 
138
        return self.compare(other, '>=')
 
139
    def __le__(self, other):
 
140
        return self.compare(other, '<=')
 
141
 
 
142
    # XXX TODO: improve error reporting
 
143
    def getQuery(self):
 
144
        return self.sql
 
145
 
 
146
    def getArgsFor(self, store):
 
147
        return [self.attribute.infilter(arg, None) for arg in self.preArgs]
 
148
 
 
149
    def getTableNames(self):
 
150
        names = [self.type.getTableName()]
 
151
        if self.otherComp is not None:
 
152
            names.append(self.otherComp.type.getTableName())
 
153
        return names
 
154
 
 
155
    def _asc(self):
 
156
        return 'ORDER BY %s.%s ASC' % (self.type.getTableName(),
 
157
                                       self.attribute.columnName)
 
158
 
 
159
    def _desc(self):
 
160
        return 'ORDER BY %s.%s DESC' % (self.type.getTableName(),
 
161
                                        self.attribute.columnName)
 
162
 
 
163
    descending = property(_desc)
 
164
    ascending = property(_asc)
 
165
    asc = ascending
 
166
    desc = descending
 
167
 
 
168
class AND:
 
169
    def __init__(self, a, b):
 
170
        self.a = a
 
171
        self.b = b
 
172
 
 
173
    def getQuery(self):
 
174
        return ' '.join(['(',
 
175
                         self.a.getQuery(),
 
176
                         self.__class__.__name__,
 
177
                         self.b.getQuery(),
 
178
                         ')'])
 
179
 
 
180
    def getArgsFor(self, store):
 
181
        return self.a.getArgsFor(store) + self.b.getArgsFor(store)
 
182
 
 
183
    def getTableNames(self):
 
184
        return (self.a.getTableNames() + self.b.getTableNames())
 
185
 
 
186
class OR(AND):
 
187
    pass
 
188
 
 
189
class integer(SQLAttribute):
 
190
    sqltype = 'INTEGER'
 
191
    def infilter(self, pyval, oself):
 
192
        if pyval is None:
 
193
            return None
 
194
        return int(pyval)
 
195
 
 
196
class bytes(SQLAttribute):
 
197
    """
 
198
    Attribute representing a sequence of bytes; this is represented in memory
 
199
    as a Python 'str'.
 
200
    """
 
201
 
 
202
    sqltype = 'BLOB'
 
203
 
 
204
    def infilter(self, pyval, oself):
 
205
        return buffer(pyval)
 
206
 
 
207
    def outfilter(self, dbval, oself):
 
208
        if dbval is None:
 
209
            return None
 
210
        return str(dbval)
 
211
 
 
212
class InvalidPathError(ValueError):
 
213
    """
 
214
    A path that could not be used with the database was attempted to be used
 
215
    with the database.
 
216
    """
 
217
 
 
218
class text(SQLAttribute):
 
219
    """
 
220
    Attribute representing a sequence of characters; this is represented in
 
221
    memory as a Python 'unicode'.
 
222
    """
 
223
 
 
224
    def __init__(self, doc='', indexed=False, caseSensitive=False):
 
225
        SQLAttribute.__init__(self, doc, indexed)
 
226
        if caseSensitive:
 
227
            self.sqltype = 'TEXT'
 
228
        else:
 
229
            self.sqltype = 'TEXT COLLATE NOCASE'
 
230
        self.caseSensitive = caseSensitive
 
231
 
 
232
    def infilter(self, pyval, oself):
 
233
        if not isinstance(pyval, unicode) or '\x00' in pyval:
 
234
            raise TypeError("text must be (unicode string without NULL bytes); not %r" %
 
235
                            (type(pyval).__name__,))
 
236
        return pyval
 
237
 
 
238
    def outfilter(self, dbval, oself):
 
239
        return dbval
 
240
 
 
241
class path(text):
 
242
 
 
243
    def infilter(self, pyval, oself):
 
244
        mypath = unicode(pyval.path)
 
245
        storepath = oself.store.filesdir
 
246
        if not mypath.startswith(storepath):
 
247
            raise InvalidPathError('%s not in %s' % (mypath, storepath))
 
248
        p = mypath[len(storepath):]
 
249
        if p.startswith('/'):
 
250
            p = p[1:]
 
251
        return super(path, self).infilter(p, oself)
 
252
 
 
253
    def outfilter(self, dbval, oself):
 
254
        if dbval is None:
 
255
            return None
 
256
        fp = FilePath(os.path.join(oself.store.filesdir, dbval))
 
257
        return fp
 
258
 
 
259
 
 
260
MICRO = 1000000.
 
261
 
 
262
class timestamp(integer):
 
263
 
 
264
    def infilter(self, pyval, oself):
 
265
        if pyval is None:
 
266
            return None
 
267
        return int(pyval.asPOSIXTimestamp() * MICRO)
 
268
 
 
269
    def outfilter(self, dbval, oself):
 
270
        if dbval is None:
 
271
            return None
 
272
        return Time.fromPOSIXTimestamp(dbval / MICRO)
 
273
 
 
274
class reference(integer):
 
275
    def __init__(self, doc='', indexed=True, reftype=None):
 
276
        integer.__init__(self, doc, indexed)
 
277
        self.reftype = reftype
 
278
 
 
279
    def infilter(self, pyval, oself):
 
280
        if pyval is None:
 
281
            return None
 
282
        if oself is None:
 
283
            return pyval.storeID
 
284
        if oself.store != pyval.store:
 
285
            raise AttributeError(
 
286
                "You can't establish references to items in other stores.")
 
287
        if oself.store is None:
 
288
            raise AttributeError(
 
289
                "TODO: Setting references on items outside of stores is "
 
290
                "currently unsupported.  Set .store first.")
 
291
        return pyval.storeID
 
292
 
 
293
    def outfilter(self, dbval, oself):
 
294
        if dbval is None:
 
295
            return None
 
296
        return oself.store.getItemByID(dbval, autoUpgrade=not oself.__legacy__)
 
297
 
 
298
# promotions