Contact: fumanchu@aminus.org

Log in as guest/geniusql to create tickets

root/trunk/geniusql/__init__.py

Revision 12 (checked in by fumanchu, 6 years ago)

Added AttributeDocstrings? metaclass.

  • Property svn:eol-style set to native
Line 
1 """Geniusql, a Python database library.
2
3 The Column and Index classes model corresponding database objects, and are
4 intentionally simple. They should rarely contain any SQL or "smarts" of
5 any kind, besides the "qname", the quoted name, of the column or index.
6 At most, subclasses and consumers might put implementation-specific data
7 into them.
8
9 The IndexSet, Table, and Database objects are all dict-like containers,
10 and therefore have a key for each value. Those keys should equate to things
11 at the consumer layer; for example, a Database may possess a pair of the
12 form: {'YoYo': Table('yoyo')} -- the key is the "friendly" name, but the
13 Table.name is a lowercase version of that, because that's what the database
14 uses in SQL to refer to that table.
15 """
16
17 __version__ = "1.0alpha"
18
19
20 class _AttributeDocstrings(type):
21     """Metaclass for declaring docstrings for class attributes."""
22     # The full docstring for this type is down in the __init__ method so
23     # that it doesn't show up in help() for every consumer class.
24    
25     def __init__(cls, name, bases, dct):
26         '''Metaclass for declaring docstrings for class attributes.
27         
28         Base Python doesn't provide any syntax for setting docstrings on
29         'data attributes' (non-callables). This metaclass allows class
30         definitions to follow the declaration of a data attribute with
31         a docstring for that attribute; the attribute docstring will be
32         popped from the class dict and folded into the class docstring.
33         
34         The naming convention for attribute docstrings is: <attrname> + "__doc".
35         For example:
36         
37             class Thing(object):
38                 """A thing and its properties."""
39                 
40                 __metaclass__ = cherrypy._AttributeDocstrings
41                 
42                 height = 50
43                 height__doc = """The height of the Thing in inches."""
44         
45         In which case, help(Thing) starts like this:
46         
47             >>> help(mod.Thing)
48             Help on class Thing in module pkg.mod:
49             
50             class Thing(__builtin__.object)
51              |  A thing and its properties.
52              | 
53              |  height [= 50]:
54              |      The height of the Thing in inches.
55              |
56         
57         The benefits of this approach over hand-edited class docstrings:
58             1. Places the docstring nearer to the attribute declaration.
59             2. Makes attribute docs more uniform ("name (default): doc").
60             3. Reduces mismatches of attribute _names_ between
61                the declaration and the documentation.
62             4. Reduces mismatches of attribute default _values_ between
63                the declaration and the documentation.
64         
65         The benefits of a metaclass approach over other approaches:
66             1. Simpler ("less magic") than interface-based solutions.
67             2. __metaclass__ can be specified at the module global level
68                for classic classes.
69         
70         The type of the attribute is intentionally not included, because
71         that's not How Python Works. Quack.
72         '''
73        
74         newdoc = [cls.__doc__ or ""]
75        
76         dctnames = dct.keys()
77         dctnames.sort()
78        
79         for name in dctnames:
80             if name.endswith("__doc"):
81                 # Remove the magic doc attribute.
82                 if hasattr(cls, name):
83                     delattr(cls, name)
84                
85                 # Get an inspect-style docstring if possible (usually so).
86                 val = dct[name]
87                 try:
88                     import inspect
89                     val = inspect.getdoc(property(doc=val)).strip()
90                 except:
91                     pass
92                
93                 # Indent the docstring.
94                 val = '\n'.join(['    ' + line.rstrip()
95                                  for line in val.split('\n')])
96                
97                 # Get the default value.
98                 attrname = name[:-5]
99                 try:
100                     attrval = getattr(cls, attrname)
101                 except AttributeError:
102                     attrval = "missing"
103                
104                 # Add the complete attribute docstring to our list.
105                 newdoc.append("%s [= %r]:\n%s" % (attrname, attrval, val))
106        
107         # Add our list of new docstrings to the class docstring.
108         cls.__doc__ = "\n\n".join(newdoc)
109
110
111 import threading
112
113 from dejavu import logic
114
115 from geniusql import errors, typerefs
116
117 from geniusql.adapters import *
118 from geniusql.conn import *
119 from geniusql.isolation import *
120 from geniusql.select import *
121
122 from geniusql import providers
123
124
125 def db(provider, **options):
126     """Return a Database and Schema object for the given provider.
127     
128     provider: A 'shortcut name' registered in geniusql.providers.registry.
129     
130     This function does not call CREATE DATABASE (although it may open a
131     database connection).
132     """
133     return providers.registry.open(provider, **options)
134
135
136 class Index:
137     """An index on a table column (or columns) in a database."""
138    
139     def __init__(self, name, qname, tablename, colname, unique=True):
140         self.name = name
141         self.qname = qname
142         self.tablename = tablename
143         self.colname = colname
144         self.unique = unique
145    
146     def __repr__(self):
147         return ("%s.%s(%r, %r, %r, %r, unique=%r)" %
148                 (self.__module__, self.__class__.__name__,
149                  self.name, self.qname, self.tablename,
150                  self.colname, self.unique))
151    
152     def __copy__(self):
153         return self.__class__(self.name, self.qname, self.tablename,
154                               self.colname, self.unique)
155     copy = __copy__
156
157
158 class IndexSet(dict):
159    
160     def __new__(cls, table):
161         return dict.__new__(cls)
162    
163     def __init__(self, table):
164         dict.__init__(self)
165         self.table = table
166    
167     def alias(self, oldname, newname):
168         """Add a new key for the Index with the given, existing key.
169         
170         Consumer code should call this method when user-supplied index
171         names do not match the names in the database. This does not
172         remove the old key; both keys may be used to refer to the same
173         Index object.
174         """
175         if oldname == newname:
176             return
177         obj = self[oldname]
178         if newname in self:
179             dict.__delitem__(self, newname)
180         dict.__delitem__(self, oldname)
181         dict.__setitem__(self, newname, obj)
182    
183     def __setitem__(self, key, index):
184         """Create the specified index."""
185         t = self.table
186         if t.created:
187             t.schema.db.execute_ddl('CREATE INDEX %s ON %s (%s);' %
188                                     (index.qname, t.qname,
189                                      t.schema.db.quote(index.colname)))
190         dict.__setitem__(self, key, index)
191    
192     def __delitem__(self, key):
193         """Drop the specified index."""
194         t = self.table
195         if t.created:
196             t.schema.db.execute_ddl('DROP INDEX %s ON %s;' %
197                              (self[key].qname, t.qname))
198         dict.__delitem__(self, key)
199
200
201 class Column:
202     """A column in a table in a database.
203     
204     name: the SQL name for this table (unquoted).
205     qname: the SQL name for this table (quoted).
206     pytype: the Python type (the actual type object, not its name).
207     dbtype: the database type name (as used in a CREATE TABLE statement).
208     default: default Python value for this column for new rows.
209     hints: a dict of implementation hints, such as precision, scale, or bytes.
210     key: True if this column is part of the table's primary key.
211     
212     imperfect_type: if True, signals that we are deliberately using a
213         database type other than the default (usually in order to handle
214         irregular values, such as huge numbers). When comparing imperfect
215         column values with constant values in SQL, the database must be
216         able to cast the column value to the constant's type. If that
217         cannot be done for the given types, then the query will be marked
218         imperfect.
219     autoincrement: if True, uses the database's built-in sequencing.
220     sequence_name: for databases that use separate statements to create and
221         drop sequences, this stores the name of the sequence.
222     initial: if autoincrement, holds the initial value for the sequence.
223     """
224    
225     def __init__(self, pytype, dbtype, default=None, hints=None, key=False,
226                  name=None, qname=None):
227         self.pytype = pytype
228         self.dbtype = dbtype
229         self.name = name
230         self.qname = qname
231         self.default = default
232         if hints is None:
233             hints = {}
234         else:
235             hints = hints.copy()
236         self.hints = hints
237         self.key = key
238        
239         # If autoincrement, the initial value should be put in self.initial.
240         self.autoincrement = False
241         self.sequence_name = None
242         self.initial = 1
243        
244         self.imperfect_type = False
245    
246     def __repr__(self):
247         return ("%s.%s(%r, %r, default=%r, hints=%r, key=%r, name=%r, qname=%r)" %
248                 (self.__module__, self.__class__.__name__,
249                  self.pytype, self.dbtype,
250                  self.default, self.hints, self.key,
251                  self.name, self.qname)
252                 )
253    
254     def __copy__(self):
255         newcol = self.__class__(self.pytype, self.dbtype,
256                                 self.default, self.hints, self.key,
257                                 self.name, self.qname)
258         newcol.autoincrement = self.autoincrement
259         newcol.initial = self.initial
260         newcol.imperfect_type = self.imperfect_type
261         return newcol
262     copy = __copy__
263
264
265 class Table(dict):
266     """A table in a database; a dict of Column objects.
267     
268     Values in this dict must be instances of Column (or a subclass of it).
269     Keys should be consumer-friendly names for each Column value.
270     
271     name: the SQL name for this table (unquoted).
272     qname: the SQL name for this table (quoted).
273     schema: the schema for this table.
274     created: whether or not this Table has a concrete implementation in the
275         database. If False (the default), then changes to Table items can be
276         made with impunity. If True, then appropriate ALTER TABLE commands
277         are executed whenever a consumer adds or deletes items from the
278         Table, or calls methods like 'rename'.
279     indices: a dict-like IndexSet of Index objects.
280     references: a dict of the form: {name: (nearColKey, farTableKey, farColKey)}.
281     """
282    
283     def __new__(cls, name, qname, schema, created=False):
284         return dict.__new__(cls)
285    
286     def __init__(self, name, qname, schema, created=False):
287         dict.__init__(self)
288        
289         self.name = name
290         self.qname = qname
291         self.schema = schema
292         self.created = created
293        
294         self.indices = schema.indexsetclass(self)
295         self.references = {}
296    
297     def __repr__(self):
298         name = getattr(self, "name", "<unknown>")
299         qname = getattr(self, "qname", "<unknown>")
300         return ("%s.%s(%r, %r)" %
301                 (self.__module__, self.__class__.__name__, name, qname))
302    
303     def __copy__(self):
304         # Don't set 'created' when copying!
305         newtable = self.__class__(self.name, self.qname, self.schema)
306         for key, c in self.iteritems():
307             dict.__setitem__(newtable, key, c.copy())
308         for key, i in self.indices.iteritems():
309             dict.__setitem__(newtable.indices, key, i.copy())
310         return newtable
311     copy = __copy__
312    
313     def alias(self, oldname, newname):
314         """Add a new key for the Column with the given, existing key.
315         
316         Consumer code should call this method when user-supplied column
317         names do not match the names in the database. This does not
318         remove the old key; both keys may be used to refer to the same
319         Column object.
320         """
321         if oldname == newname:
322             return
323        
324         obj = self[oldname]
325         if newname in self:
326             dict.__delitem__(self, newname)
327         dict.__delitem__(self, oldname)
328         dict.__setitem__(self, newname, obj)
329    
330     def _add_column(self, column):
331         """Internal function to add the column to the database."""
332         coldef = self.schema.columnclause(column)
333         self.schema.db.execute("ALTER TABLE %s ADD COLUMN %s;" %
334                                (self.qname, coldef))
335    
336     def __setitem__(self, key, column):
337         if column.name is None:
338             column.name = self.schema._column_name(self.name, key)
339             column.qname = self.schema.db.quote(column.name)
340        
341         if not self.created:
342             dict.__setitem__(self, key, column)
343             return
344        
345         if key in self:
346             del self[key]
347        
348         if column.autoincrement:
349             # This may or may not be a no-op, depending on the DB.
350             self.schema.create_sequence(self, column)
351         self._add_column(column)
352         dict.__setitem__(self, key, column)
353    
354     def _drop_column(self, column):
355         """Internal function to drop the column from the database."""
356         self.schema.db.execute_ddl("ALTER TABLE %s DROP COLUMN %s;" %
357                                    (self.qname, column.qname))
358    
359     def __delitem__(self, key):
360         if key in self.indices:
361             del self.indices[key]
362        
363         if not self.created:
364             dict.__delitem__(self, key)
365             return
366        
367         column = self[key]
368         self._drop_column(column)
369         if column.autoincrement:
370             # This may or may not be a no-op, depending on the DB.
371             self.schema.drop_sequence(column)
372         dict.__delitem__(self, key)
373    
374     def _rename(self, oldcol, newcol):
375         # Override this to do the actual rename at the DB level.
376         self.schema.db.execute_ddl("ALTER TABLE %s RENAME COLUMN %s TO %s;" %
377                                    (self.qname, oldcol.qname, newcol.qname))
378    
379     def rename(self, oldkey, newkey):
380         """Rename a Column. This will change the table name in the database."""
381         oldcol = self[oldkey]
382        
383         if not self.created:
384             dict.__delitem__(self, oldkey)
385             dict.__setitem__(self, newkey, oldcol)
386             return
387        
388         oldname = oldcol.name
389         newname = self.schema._column_name(self.name, newkey)
390        
391         if oldname != newname:
392             newcol = oldcol.copy()
393             newcol.name = newname
394             newcol.qname = self.schema.db.quote(newname)
395             self._rename(oldcol, newcol)
396        
397         # Use the superclass calls to avoid DROP COLUMN/ADD COLUMN.
398         dict.__delitem__(self, oldkey)
399         dict.__setitem__(self, newkey, newcol)
400    
401     def add_index(self, columnkey):
402         """Add and return a new Index for the given column key.
403         
404         The new Index object will possess the same key as the column.
405         In general, the actual SQL name of the new Index will be of
406         the form: "i" + table.name + column.name.
407         """
408         colname = self[columnkey].name
409         name = self.schema.table_name("i" + self.name + colname)
410         i = Index(name, self.schema.db.quote(name), self.name, colname)
411         self.indices[columnkey] = i
412         return i
413    
414    
415     # ---------------------------- OLTP/CRUD ---------------------------- #
416    
417     def whereclause(self, **inputs):
418         """Return an SQL WHERE clause for the given input fields.
419         
420         If the given clause is imperfect, a ValueError is raised.
421         """
422         tpair = [(self.qname, self)]
423         decom = self.schema.db.decompiler(tpair, logic.filter(**inputs),
424                                           self.schema.db.adaptertosql)
425         code = decom.code()
426         if decom.imperfect:
427             raise ValueError("The given inputs could not safely be translated "
428                              "to SQL.", inputs, code)
429         return code
430    
431     def id_clause(self, **inputs):
432         """Return an SQL expression for the identifiers of the given table."""
433         for key in inputs.keys():
434             if not self[key].key:
435                 inputs.pop(key)
436         return self.whereclause(**inputs)
437    
438     def insert(self, **inputs):
439         """Insert a row and return {idcolkey: newid}."""
440         coerce_out = self.schema.db.adaptertosql.coerce
441         coerce_in = self.schema.db.adapterfromdb.coerce
442        
443         fields = []
444         idkeys = []
445         values = []
446         for key, col in self.iteritems():
447             if col.autoincrement:
448                 # Skip this field, since we're using a sequencer
449                 idkeys.append(key)
450                 continue
451             if key in inputs:
452                 val = coerce_out(inputs[key], col.dbtype)
453                 fields.append(col.qname)
454                 values.append(val)
455        
456         conn = self.schema.db.connections.get()
457        
458         fields = ", ".join(fields)
459         values = ", ".join(values)
460         self.schema.db.execute('INSERT INTO %s (%s) VALUES (%s);' %
461                                (self.qname, fields, values), conn)
462        
463         if idkeys:
464             newids = self._grab_new_ids(idkeys, conn)
465             for key in newids.keys():
466                 col = self[key]
467                 newids[key] = coerce_in(newids[key], col.dbtype, col.pytype)
468             return newids
469         else:
470             return {}
471    
472     def _grab_new_ids(self, idkeys, conn):
473         # Override this to fetch and return new autoincrement values.
474         raise NotImplementedError
475    
476     def save(self, **inputs):
477         """Update a row using the given inputs."""
478         parms = []
479         coerce = self.schema.db.adaptertosql.coerce
480         for key, val in inputs.iteritems():
481             col = self[key]
482             if col.autoincrement:
483                 # Skip this field, since we're using a sequencer
484                 pass
485             else:
486                 val = coerce(val, col.dbtype)
487                 parms.append('%s = %s' % (col.qname, val))
488        
489         if parms:
490             sql = ('UPDATE %s SET %s WHERE %s;' %
491                    (self.qname, ", ".join(parms), self.id_clause(**inputs)))
492             self.schema.db.execute(sql)
493    
494     use_asterisk_to_delete_all = False
495    
496     def delete(self, **inputs):
497         """Delete all rows matching the given identifier inputs."""
498         if self.use_asterisk_to_delete_all:
499             star = " *"
500         else:
501             star = ""
502         self.schema.db.execute('DELETE%s FROM %s WHERE %s;' %
503                                (star, self.qname, self.id_clause(**inputs)))
504    
505     def delete_all(self, **inputs):
506         """Delete all rows matching the given inputs."""
507         if self.use_asterisk_to_delete_all:
508             star = " *"
509         else:
510             star = ""
511         self.schema.db.execute('DELETE%s FROM %s WHERE %s;' %
512                                (star, self.qname,
513                                 self.whereclause(**inputs)))
514    
515     def select_all(self, restriction=None, **kwargs):
516         """Yield data dicts matching the given restriction."""
517         if restriction and not isinstance(restriction, logic.Expression):
518             restriction = logic.Expression(restriction)
519         if kwargs:
520             f = logic.filter(**kwargs)
521             if restriction:
522                 restriction += f
523             else:
524                 restriction = f
525        
526         attrs = self.keys()
527         data = self.schema.db.select(self, attrs, restriction)
528         for row in data:
529             row = dict(zip(attrs, row))
530             if restriction and data.imperfect:
531                 # Run a dummy object through our restriction before yielding.
532                 if not restriction(ImperfectDummy(**row)):
533                     continue
534             yield row
535    
536     def select_one(self, restriction=None, **kwargs):
537         """Return a single data dict matching the given restriction (or None)."""
538         try:
539             return self.select_all(restriction, **kwargs).next()
540         except StopIteration:
541             return None
542
543
544 class ImperfectDummy(object):
545     """A dummy object for resolving imperfect queries."""
546     def __init__(self, **kwargs):
547         for k, v in kwargs.iteritems():
548             setattr(self, k, v)
549
550
551 class Schema(dict):
552     """A dict for managing a set of tables.
553     
554     Values in this dict must be instances of Table. Keys should be
555     consumer-friendly names for each Table value. For example, it's
556     easiest to use all lowercase table names in MySQL; however, a
557     geniusql consumer might want their code to use TitledNames to
558     refer to each table.
559     
560     When a consumer adds and deletes items from a Schema object,
561     appropriate CREATE TABLE/DROP TABLE commands are executed.
562     This means that a Table object to be added should have all
563     of its columns populated before adding it to the Schema.
564     """
565    
566     tableclass = Table
567     indexsetclass = IndexSet
568    
569     def __new__(cls, db, name):
570         return dict.__new__(cls)
571    
572     def __init__(self, db, name):
573         dict.__init__(self)
574        
575         self.db = db
576         self.name = self.db.sql_name(name)
577         self.qname = self.db.quote(self.name)
578         self._discover_lock = threading.Lock()
579         self.discover_dbinfo()
580    
581     def __repr__(self):
582         name = getattr(self, "name", "<unknown>")
583         return "%s.%s(%r)" % (self.__module__, self.__class__.__name__, name)
584    
585     #                              Discovery                              #
586    
587     def _get_dbinfo(self, conn=None):
588         return {}
589    
590     def discover_dbinfo(self, conn=None):
591         """Set attributes on self with actual DB metadata, where possible."""
592         for k, v in self._get_dbinfo().iteritems():
593             setattr(self, k, v)
594    
595     def _get_tables(self, conn=None):
596         raise NotImplementedError
597    
598     def _get_table(self, tablename, conn=None):
599         # Fallback behavior. This is slow and should be optimized by each DB.
600         for t in self._get_tables():
601             if t.name == tablename:
602                 return t
603         raise errors.MappingError(tablename)
604    
605     def _get_columns(self, tablename, conn=None):
606         raise NotImplementedError
607    
608     def _get_indices(self, tablename, conn=None):
609         raise NotImplementedError
610    
611     def _discover_table(self, table, conn=None):
612         """Populate the columns and indices of the given Table object."""
613         for col in self._get_columns(table.name, conn):
614             # Use the superclass call to avoid ALTER TABLE
615             if col.name in table:
616                 dict.__delitem__(table, col.name)
617             dict.__setitem__(table, col.name, col)
618        
619         for idx in self._get_indices(table.name, conn):
620             # Use the superclass call to avoid CREATE INDEX
621             if idx.name in table.indices:
622                 dict.__delitem__(table.indices, idx.name)
623             dict.__setitem__(table.indices, idx.name, idx)
624    
625     def discover(self, tablename, conn=None):
626         """Attach a new Table from the underlying DB to self (and return it).
627         
628         Table objects (and their Column and Index subobjects) will be
629         added to self using keys that match the database's names.
630         Consumers should call the "alias(oldname, newname)" method
631         of Schema, Table, and IndexSet in order to re-map the
632         discovered objects using consumer-friendly names.
633         
634         If no such table exists, a MappingError should be raised.
635         """
636         self._discover_lock.acquire()
637         try:
638             table = self._get_table(tablename)
639             self._discover_table(table, conn)
640            
641             # Use the superclass calls to avoid CREATE TABLE
642             if table.name in self:
643                 dict.__delitem__(self, table.name)
644             dict.__setitem__(self, table.name, table)
645            
646             return table
647         finally:
648             self._discover_lock.release()
649    
650     def discover_all(self, conn=None):
651         """(Re-)populate self (all table items) from the underlying DB.
652         
653         Table objects (and their Column and Index subobjects) will be
654         added to self using keys that match the database's names.
655         Consumers should call the "alias(oldname, newname)" method
656         of Schema, Table, and IndexSet in order to re-map the
657         discovered objects using consumer-friendly names.
658         
659         This method is idempotent, but that doesn't mean cheap. Try not
660         to call it very often (once at app startup is usually enough).
661         If you already know the names of all the tables you want to
662         discover, it's often faster to skip this method and just use
663         the discover(tablename) method for each known name instead.
664         """
665         self._discover_lock.acquire()
666         try:
667             for table in self._get_tables(conn):
668                 self._discover_table(table, conn)
669                
670                 # Use the superclass calls to avoid CREATE TABLE
671                 if table.name in self:
672                     dict.__delitem__(self, table.name)
673                 dict.__setitem__(self, table.name, table)
674         finally:
675             self._discover_lock.release()
676    
677     def alias(self, oldname, newname):
678         """Add a new key for the Table with the given, existing key.
679         
680         Consumer code should call this method when user-supplied table
681         names do not match the names in the database. This does not
682         remove the old key; both keys may be used to refer to the same
683         Table object.
684         """
685         if oldname == newname:
686             return
687        
688         obj = self[oldname]
689         if newname in self:
690             dict.__delitem__(self, newname)
691         dict.__delitem__(self, oldname)
692         dict.__setitem__(self, newname, obj)
693    
694     def _column_name(self, tablename, columnkey):
695         "Return the SQL column name for the given table name and column key."
696         # If you want to use a map from your ORM's property names
697         # to DB column names, override this method (that's why
698         # the tablename must be included in the args).
699         return self.db.sql_name(columnkey)
700    
701     def column(self, pytype=unicode, dbtype=None, default=None, hints=None,
702                key=False, autoincrement=False):
703         """Return a Column object from the given arguments."""
704         col = Column(pytype, dbtype, default, hints, key)
705         col.autoincrement = autoincrement
706        
707         if dbtype is None:
708             col.dbtype = self.db.typeadapter.coerce(col, pytype)
709         pytype2 = self.db.python_type(col.dbtype)
710         col.imperfect_type = not self.db.isrelatedtype(pytype, pytype2)
711        
712         return col
713    
714     prefix = ""
715    
716     def table_name(self, key):
717         """Return the SQL table name for the given key."""
718         # If you want to use a map from your ORM's class names
719         # to DB table names, override this method.
720         return self.db.sql_name(self.prefix + key)
721    
722     def table(self, name):
723         """Create and return a Table object for the given name."""
724         name = self.table_name(name)
725         return self.tableclass(name, self.db.quote(name), self)
726    
727     def create_sequence(self, table, column):
728         """Create a SEQUENCE for the given column and set its sequence_name."""
729         # By default, this does nothing. Databases which require a separate
730         # statement to create a sequence generator should override this.
731         pass
732    
733     def drop_sequence(self, column):
734         """Drop a SEQUENCE for the given column and remove its sequence_name."""
735         # By default, this does nothing. Databases which require a separate
736         # statement to drop a sequence generator should override this.
737         pass
738    
739     def columnclause(self, column):
740         """Return a clause for the given column for CREATE or ALTER TABLE.
741         
742         This will be of the form "name type [DEFAULT x]".
743         
744         Most subclasses will override this to add autoincrement support.
745         """
746         dbtype = column.dbtype
747        
748         default = column.default or ""
749         if default:
750             default = self.db.adaptertosql.coerce(default, dbtype)
751             default = " DEFAULT %s" % default
752        
753         return "%s %s%s" % (column.qname, dbtype, default)
754    
755     def __setitem__(self, key, table):
756         if key in self:
757             del self[key]
758        
759         # Set table.created to True, which should "turn on"
760         # any future ALTER TABLE statements.
761         table.created = True
762        
763         fields = []
764         pk = []
765         for column in table.itervalues():
766             if column.autoincrement:
767                 # This may or may not be a no-op, depending on the DB.
768                 self.create_sequence(table, column)
769            
770             fields.append(self.columnclause(column))
771             if column.key:
772                 pk.append(column.qname)
773        
774         if pk:
775             pk = ", PRIMARY KEY (%s)" % ", ".join(pk)
776         else:
777             pk = ""
778        
779         self.db.execute_ddl('CREATE TABLE %s (%s%s);' %
780                             (table.qname, ", ".join(fields), pk))
781        
782         for index in table.indices.itervalues():
783             self.db.execute_ddl('CREATE INDEX %s ON %s (%s);' %
784                                 (index.qname, table.qname,
785                                  self.db.quote(index.colname)))
786        
787         dict.__setitem__(self, key, table)
788    
789     def __delitem__(self, key):
790         table = self[key]
791         self.db.execute_ddl('DROP TABLE %s;' % table.qname)
792         for col in table.itervalues():
793             if col.autoincrement:
794                 self.drop_sequence(col)
795         dict.__delitem__(self, key)
796    
797     def _rename(self, oldtable, newtable):
798         # Override this to do the actual rename at the DB level.
799         raise NotImplementedError
800         newtable.created = True
801    
802     def rename(self, oldkey, newkey):
803         """Rename a Table."""
804         oldtable = self[oldkey]
805         oldname = oldtable.name
806         newname = self.db.table_name(newkey)
807        
808         if oldname != newname:
809             newtable = oldtable.copy()
810             newtable.schema = self.schema
811             newtable.name = newname
812             newtable.qname = self.db.quote(newname)
813             self._rename(oldtable, newname)
814        
815         # Use the superclass calls to avoid DROP TABLE/CREATE TABLE.
816         dict.__delitem__(self, oldkey)
817         dict.__setitem__(self, newkey, newtable)
818    
819     def create_database(self):
820         self.db.execute_ddl("CREATE DATABASE %s;" % self.qname)
821         self.clear()
822    
823     def drop_database(self):
824         # Must shut down all connections to avoid
825         # "being accessed by other users" error.
826         self.db.connections.shutdown()
827         self.db.execute_ddl("DROP DATABASE %s;" % self.qname)
828         self.clear()
829
830
831 class Database(object):
832    
833     __metaclass__ = _AttributeDocstrings
834    
835     adaptertosql = AdapterToSQL()
836     adapterfromdb = AdapterFromDB()
837     typeadapter = TypeAdapter()
838     decompiler = SQLDecompiler
839     joinwrapper = TableWrapper
840     selectwriter = SelectWriter
841     connectionmanager = ConnectionManager
842     schemaclass = Schema
843    
844     multischema = True
845     multischema__doc = """If True, instances of this Database class may
846     spawn multiple Schema instances. This is False, for example, when
847     the underlying Database engine binds connections to individual files.
848     In most applications (that use a single schema) this presents no
849     problems; applications that need to handle more than one schema
850     at a time should inspect this value to determine whether they
851     need a separate Database instance per Schema instance.
852     """
853    
854     def __init__(self, **kwargs):
855         for k, v in kwargs.iteritems():
856             setattr(self, k, v)
857        
858         poolsize = kwargs.get('poolsize', 10)
859         self.connections = self.connectionmanager(self, poolsize)
860    
861     def version(self):
862         """Return a string containing version info for this database."""
863         raise NotImplementedError
864    
865     def log(self, msg):
866         pass
867    
868     def python_type(self, dbtype):
869         """Return a Python type which can store values of the given dbtype."""
870         raise TypeError("Database type %r could not be converted "
871                         "to a Python type." % dbtype)
872    
873     def isrelatedtype(self, pytype1, pytype2):
874         """If values of both types are expressed with the same SQL, return True."""
875         if issubclass(pytype1, pytype2) or issubclass(pytype2, pytype1):
876             return True
877         if issubclass(pytype1, basestring) and issubclass(pytype2, basestring):
878             return True
879         if ((issubclass(pytype1, int) or issubclass(pytype1, long)) and
880             (issubclass(pytype2, int) or issubclass(pytype2, long))):
881             return True
882         if typerefs.fixedpoint:
883             if typerefs.decimal:
884                 if ((issubclass(pytype1, typerefs.fixedpoint.FixedPoint)
885                      or issubclass(pytype1, typerefs.decimal.Decimal)) and
886                     (issubclass(pytype2, typerefs.fixedpoint.FixedPoint)
887                      or issubclass(pytype2, typerefs.decimal.Decimal))):
888                     return True
889             else:
890                 if (issubclass(pytype1, typerefs.fixedpoint.FixedPoint) and
891                     issubclass(pytype2, typerefs.fixedpoint.FixedPoint)):
892                     return True
893         else:
894             if typerefs.decimal:
895                 if (issubclass(pytype1, typerefs.decimal.Decimal) and
896                     issubclass(pytype2, typerefs.decimal.Decimal)):
897                     return True
898         return False
899    
900     #                               Naming                               #
901    
902     sql_name_max_length = 64
903     sql_name_caseless = False
904    
905     def quote(self, name):
906         """Return name, quoted for use in an SQL statement."""
907         # This base class doesn't use "quote",
908         # but most subclasses will.
909         return name
910    
911     def sql_name(self, key):
912         """Return the native SQL version of key."""
913         if self.sql_name_caseless:
914             key = key.lower()
915        
916         maxlen = self.sql_name_max_length
917         if maxlen and len(key) > maxlen:
918             errors.warn("The name '%s' is longer than the maximum of "
919                         "%s characters." % (key, maxlen))
920             key = key[:maxlen]
921        
922         return key
923    
924     def is_timeout_error(self, exc):
925         """If the given exception instance is a lock timeout, return True.
926         
927         This should return True for errors which arise from locking
928         timeouts; for example, if the database prevents 'dirty reads'
929         by raising an error.
930         """
931         # You should definitely override this for your database.
932         return False
933    
934     def execute(self, query, conn=None):
935         """Return a native response for the given query."""
936         if conn is None:
937             conn = self.connections.get()
938         if isinstance(query, unicode):
939             query = query.encode(self.adaptertosql.encoding)
940         self.log(query)
941         return conn.query(query)
942    
943     def execute_ddl(self, query, conn=None):
944         """Return a native response for the given DDL statement.
945         
946         In general, DDL statements should lock out other statements
947         (especially those isolated in other transactions). Use this
948         method to perform a locked DDL statement.
949         """
950         self.connections.lock("Transaction denied due to DDL: %r" % query)
951         try:
952 ##            # Must shut down all connections to avoid
953 ##            # "being accessed by other users" error?
954 ##            self.connections.shutdown()
955             if conn is None:
956                 # Important: use _factory(), not get(), to avoid the lock
957                 conn = self.connections._factory()
958             self.execute(query, conn)
959         finally:
960             self.connections.unlock()
961    
962     def fetch(self, query, conn=None):
963         """Return rowdata, columns (name, type) for the given query.
964         
965         query should be a SQL query in string format
966         rowdata will be an iterable of iterables containing the result values.
967         columns will be an iterable of (column name, data type) pairs.
968         
969         This base class uses _sqlite syntax.
970         """
971         res = self.execute(query, conn)
972         return res.row_list, res.col_defs
973    
974     def select(self, relation, attributes, restriction=None, distinct=False):
975         """Yield matching data, coerced to Python types (where known)."""
976         sel = self.selectwriter(self, relation, attributes, restriction)
977         data, _ = self.fetch(sel.sql(distinct))
978         return ResultSet(data, sel.columns, sel.imperfect)
979    
980     def schema(self, name):
981         return self.schemaclass(self, name)
982
983
984 class ResultSet:
985    
986     def __init__(self, data, columns, imperfect):
987         self.data = data
988         self.columns = columns
989         self.imperfect = imperfect
990         self.cursor = 0
991    
992     def __iter__(self):
993         return self
994    
995     def next(self):
996         try:
997             row = self.data[self.cursor]
998             self.cursor += 1
999         except IndexError:
1000             raise StopIteration
1001        
1002         coerced_row = []
1003         for i, (table, col, qname) in enumerate(self.columns):
1004             val = row[i]
1005             if table and col:
1006                 val = table.schema.db.adapterfromdb.coerce(val, col.dbtype, col.pytype)
1007             coerced_row.append(val)
1008         return coerced_row
1009
Note: See TracBrowser for help on using the browser.