Package orm2 :: Module datasource
[hide private]
[frames] | no frames]

Source Code for Module orm2.datasource

  1  #!/usr/bin/env python 
  2  # -*- coding: iso-8859-1 -*- 
  3   
  4  ##  This file is part of orm, The Object Relational Membrane Version 2. 
  5  ## 
  6  ##  Copyright 2002-2006 by Diedrich Vorberg <diedrich@tux4web.de> 
  7  ## 
  8  ##  All Rights Reserved 
  9  ## 
 10  ##  For more Information on orm see the README file. 
 11  ## 
 12  ##  This program is free software; you can redistribute it and/or modify 
 13  ##  it under the terms of the GNU General Public License as published by 
 14  ##  the Free Software Foundation; either version 2 of the License, or 
 15  ##  (at your option) any later version. 
 16  ## 
 17  ##  This program is distributed in the hope that it will be useful, 
 18  ##  but WITHOUT ANY WARRANTY; without even the implied warranty of 
 19  ##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 20  ##  GNU General Public License for more details. 
 21  ## 
 22  ##  You should have received a copy of the GNU General Public License 
 23  ##  along with this program; if not, write to the Free Software 
 24  ##  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 25  ## 
 26  ##  I have added a copy of the GPL in the file gpl.txt. 
 27   
 28  # Changelog 
 29  # --------- 
 30  # 
 31  # $Log: datasource.py,v $ 
 32  # Revision 1.18  2007/05/14 19:47:07  diedrich 
 33  # Fixed select_one(). 
 34  # 
 35  # Revision 1.17  2006/10/07 22:00:55  diedrich 
 36  # Print both command and params to sqllog 
 37  # 
 38  # Revision 1.16  2006/09/05 16:54:00  diedrich 
 39  # Of course, Python's database API doesn't support ? as a placeholder 
 40  # but %s. This also means that all %s must be escaped in input SQL. This 
 41  # remains untested for firebird. 
 42  # 
 43  # Revision 1.15  2006/09/04 15:50:32  diedrich 
 44  # Updates to work with new sql.sql() class (functions to use "?" syntax in 
 45  # calls to cursor.execute()). 
 46  # 
 47  # Revision 1.14  2006/07/08 17:17:50  diedrich 
 48  # Added select_for_update() method 
 49  # 
 50  # Revision 1.13  2006/06/11 23:46:05  diedrich 
 51  # Added delete_by_primary_key() 
 52  # 
 53  # Revision 1.12  2006/06/09 09:04:20  diedrich 
 54  # Use dbclass.__result__ for results. 
 55  # 
 56  # Revision 1.11  2006/05/13 17:23:42  diedrich 
 57  # Massive docstring update. 
 58  # 
 59  # Revision 1.10  2006/05/10 21:54:15  diedrich 
 60  # Fixed a bug in flush_updates() (why did this have a cursor, 
 61  # attribute, anyway?) 
 62  # 
 63  # Revision 1.9  2006/04/28 08:38:30  diedrich 
 64  # Added close() method. 
 65  # 
 66  # Revision 1.8  2006/04/15 23:15:07  diedrich 
 67  # Split up select() in select() and run_select() 
 68  # 
 69  # Revision 1.7  2006/02/25 17:59:55  diedrich 
 70  # Made the many2one work with multi column keys. 
 71  # 
 72  # Revision 1.6  2006/02/25 00:20:20  diedrich 
 73  # - Added and tested the ability to use multiple column primary keys. 
 74  # - Some small misc bugs. 
 75  # 
 76  # Revision 1.5  2006/01/01 20:38:59  diedrich 
 77  # Implemented the update machanism. Numerous misc fixes. 
 78  # 
 79  # Revision 1.4  2005/12/31 18:21:33  diedrich 
 80  # - Updated year in copyright header ;) 
 81  # - added gadfly adapter 
 82  # - added select_one() method to datasource 
 83  # - took care of situationsin which two properties refer to the same column 
 84  # 
 85  # Revision 1.3  2005/12/18 22:35:46  diedrich 
 86  # - Inheritance 
 87  # - pgsql adapter 
 88  # - first unit tests 
 89  # - some more comments 
 90  # 
 91  # Revision 1.2  2005/11/21 19:55:12  diedrich 
 92  # - put the orm2.sql specific stuff into orm2.sql (where it belongs) 
 93  # - wrote the insert() method 
 94  # 
 95  # Revision 1.1.1.1  2005/11/20 14:55:46  diedrich 
 96  # Initial import 
 97  # 
 98   
 99  __docformat__ = "epytext en" 
100   
101  """ 
102  Defines abstract class datasource, baseclass for adapter.*.datasource. 
103   
104  The datasource module defines the datasource class and a number of 
105  conveniance classes for managing query results. 
106  """ 
107   
108  # Python 
109  from types import * 
110  import string 
111   
112  # orm 
113  from orm2.debug import sqllog, debug 
114  from orm2.exceptions import * 
115  from orm2 import sql, keys 
116  from orm2.util import stupid_dict 
117   
118 -def datasource(connection_string="", **kwargs):
119 """ 120 Return a ORM datasource object constructed from a connection 121 string or a number of keyword arguments. 122 123 The connection strings follow the conventions for PostgreSQL DSNs: 124 they consist of keyword=value pairs seperated with whitespace. 125 Keywords recognized are:: 126 127 adapter - name of the ORM adapter used. Use the name from the 128 adapters/ directory. 129 db - name of the database to connect to 130 user - Database username 131 password - Password used for authentication 132 host - hostname or IP address of the machine the database 133 runs on (note that there might be a difference if you 134 use 127.0.0.1 or localhost. The first creating a tcp/ip 135 connection, the latter a unix/fifo connection. This is 136 true for at leas pgsql and mysql 137 debug - if set SQL queries will be printed to stdout (actually 138 the debug.debug function is called so you can overload 139 it) 140 141 Each of the database backends may define its own keywords. For 142 instance PostgreSQL will understand each of the original keywords 143 as aliases. Check the documentation! 144 145 Values may not contain spaces. 146 147 If you prefer to use the keyword argument syntax, the paramters must 148 be the key and their arguments the values:: 149 150 datasource('db=test user=diedrich password=kfjdh') 151 152 equals:: 153 154 datasource(db='test', user='diedrich', password='kfjdh') 155 """ 156 157 try: 158 parts = string.splitfields(connection_string) 159 params = {} 160 for part in parts: 161 name, value = string.split(part, "=") 162 if name != "" and value != "": 163 params[name] = value 164 else: 165 raise ValueError() 166 except ValueError, msg: 167 raise IllegalConnectionString("%s (%s)" % (connection_string, 168 msg)) 169 170 params.update(kwargs) 171 172 try: 173 adapter = params["adapter"] 174 except KeyError: 175 raise IllegalConnectionString( 176 "%s (The adapter= keyword must always be present!)" %connection_string) 177 178 del params["adapter"] 179 180 if adapter == "gadfly": 181 from orm2.adapters.gadfly.datasource import datasource 182 elif adapter == "pgsql": 183 from orm2.adapters.pgsql.datasource import datasource 184 elif adapter == "mysql": 185 from orm2.adapters.mysql.datasource import datasource 186 elif adapter == "firebird": 187 from orm2.adapters.firebird.datasource import datasource 188 else: 189 raise IllegalConnectionString("Unknown adapter: %s" % adapter) 190 191 if params.has_key("debug"): 192 debug = True 193 del params["debug"] 194 else: 195 debug = False 196 197 ds = datasource.from_params(params) 198 ds._debug = debug 199 200 return ds
201
202 -class datasource_base(sql.datasource):
203 """ 204 The DataSource encapsulates the functionality we need to talk to the 205 database. Most notably are the insert, select and delete methods. 206 207 This class must be subclassed by the adapter.*.datasource.datasource 208 classes. 209 210 It inherits from sql.datasource to provide default implmentations of 211 the methods the sql module depends upon. 212 """ 213 214 escaped_chars = [ ("\\", "\\\\"), 215 ("'", "\\'"), 216 ('"', '\\"'), 217 ("%", "%%",), ] 218 219 _format_funcs = {} 220
221 - def __init__(self):
222 self._conn = None 223 self._updates = stupid_dict() 224 self._update_cursor = None 225 self._debug = 0
226
227 - def _dbconn(self):
228 """ 229 Return the dbconn for this ds 230 """ 231 return self._conn
232
233 - def query_one(self, query):
234 """ 235 This method is ment for results that return exactly one row or item 236 237 It will: 238 239 - return None if there is an empty result returned 240 - if there are more than one result row, return the result as is 241 (a tuple of tuples) 242 - if there is only one row, but several columns, return the row as 243 a tuple 244 - if the only row has only one column, return the value of the 245 column 246 247 @param query: A string containing an SQL query. 248 """ 249 cursor = self.execute(query) 250 result = cursor.fetchone() 251 252 try: 253 if len(result) == 1: return result[0] 254 except TypeError: # result has no size 255 return result
256
257 - def execute(self, command, modify=False):
258 """ 259 Execute COMMAND on the database. If modify is True, the command 260 is assumed to modify the database. All modifying commands 261 will be executed on the same cursor. 262 263 @param command: A string containing an SQL command of any kind or an 264 sql.statement instance. 265 """ 266 if type(command) == UnicodeType: 267 raise TypeError("Database queries must be strings, not unicode") 268 269 if modify: 270 cursor = self.update_cursor() 271 else: 272 cursor = self._dbconn().cursor() 273 274 if isinstance(command, sql.statement): 275 runner = sql.sql(self) 276 command = runner(command) 277 params = runner.params 278 else: 279 params = () 280 281 print >> sqllog, "command:", command, "params", repr(params) 282 283 cursor.execute(command, tuple(params)) 284 285 # print >> debug, "back!" 286 287 return cursor
288
289 - def commit(self):
290 """ 291 Run commit on this ds's connection. You need to do this for any 292 change you really want to happen! 293 """ 294 cursor = self.flush_updates() 295 self._dbconn().commit()
296
297 - def rollback(self):
298 """ 299 Undo the changes you made to the database since the last commit() 300 """ 301 self._updates.clear() 302 self._dbconn().rollback()
303
304 - def cursor(self):
305 """ 306 Return a newly created dbi cursor. 307 """ 308 return self._dbconn().cursor()
309
310 - def close(self):
311 """ 312 Close the connection to the database. 313 """ 314 self._dbconn().close()
315
316 - def update_cursor(self):
317 """ 318 Return the cursor that this ds uses for any query that modifies 319 the database (to keep the transaction together). 320 """ 321 if getattr(self, "_update_cursor", None) is None: 322 self._update_cursor = self.cursor() 323 324 return self._update_cursor
325 326
327 - def select(self, dbclass, *clauses):
328 """ 329 SELECT dbobjs from the database, according to clauses. 330 331 @param dbclass: The dbclass of the objects to be selected. 332 333 @param clauses: A list of orm2.sql clauses instances (or 334 equivalent Python object i.e. strings) that 335 are added to the sql.select query. See 336 orm2.sql.select for details 337 338 339 """ 340 query = sql.select(dbclass.__select_columns__(), 341 dbclass.__relation__, *clauses) 342 343 return self.run_select(dbclass, query)
344 345
346 - def run_select(self, dbclass, select):
347 """ 348 Run a select statement on this datasource that is ment to return 349 rows suitable to construct objects of dbclass from them. 350 351 @param dbclass: The dbclass of the objects to be selected 352 @param select: sql.select instance representing the query 353 """ 354 return dbclass.__result__(self, dbclass, select)
355
356 - def select_one(self, dbclass, *clauses):
357 """ 358 This method is ment for queries of which you know that they 359 will return exactly one dbobj. It will set a limit=1 clause. 360 If the result is empty, it will return None, otherwise the 361 selected dbobj. 362 """ 363 clauses += (sql.limit(1),) 364 365 result = self.select(dbclass, *clauses) 366 367 try: 368 return result.next() 369 except StopIteration: 370 return None
371
372 - def count(self, dbclass, *clauses):
373 """ 374 All clauses except the WHERE clause will be ignored 375 (including OFFSET and LIMIT!) 376 377 @param dbclass: See select() above. 378 @param clauses: See select() above. 379 380 @return: An integer value indicating the number of objects 381 of dbclass select() would return if run with these clauses. 382 """ 383 384 where = None 385 for clause in clauses: 386 if isinstance(clause, sql.where): 387 where = clause 388 389 if where is not None: 390 clauses = [where] 391 else: 392 clauses = [] 393 394 query = sql.select("COUNT(*)", dbclass.__relation__, *clauses) 395 return self.query_one(query)
396
397 - def join_select(self, dbclasses, *clauses):
398 # this may take some figuring 399 pass
400
401 - def primary_key_where(self, dbclass, key):
402 """ 403 Return a orm2.sql where clause that will yield the object of dbclass 404 whoes primary key equals key 405 406 @param dbclass: The dbclass of the object the where clause is 407 supposed to be for. 408 @param key: Python value representing the primary key or a tuple of 409 such Python values, if the primary key has multiple columns 410 """ 411 412 # this function is very simmilar to keys.key.where() - maybe unify? 413 414 if type(key) != TupleType: key = ( key, ) 415 primary_key = keys.primary_key(dbclass) 416 417 if len(key) != len(primary_key.key_attributes): 418 msg = "The primary key for %s must have %i elements." % \ 419 ( repr(dbclass), len(primary_key.key_attributes), ) 420 raise IllegalPrimaryKey(msg) 421 422 where = [] 423 for property, value in zip(primary_key.attributes(), key): 424 where.append(property.column) 425 where.append("=") 426 where.append(property.sql_literal_class(value)) 427 where.append("AND") 428 429 del where[-1] # remove last "AND" 430 431 return sql.where(*where)
432
433 - def select_by_primary_key(self, dbclass, key):
434 """ 435 Select a single object of dbclass from its relation, identified 436 by its primary key. 437 438 @param dbclass: Dbclass to be selected 439 @param key: Python value representing the primary key or a tuple of 440 such Python values, if the primary key has multiple columns 441 @raise IllegalPrimaryKey: hallo 442 @return: A single dbobj. 443 """ 444 where = self.primary_key_where(dbclass, key) 445 result = self.select(dbclass, where) 446 447 try: 448 return result.next() 449 except StopIteration: 450 return None
451
452 - def select_for_update(self, dbclass, key):
453 """ 454 This method works like L{select_by_primary_key} above, except that it 455 doesn't select anything but returns a dummy object (an empty dbobj) 456 that will allow setting attributes, yielding proper UPDATE statements. 457 Note that supplying a primary key that does not exist will go 458 unnoticed: The UPDATE statements won't create an error, they just 459 won't affect any rows. 460 461 This method is primarily ment for transaction based (i.e. www) 462 applications. 463 """ 464 if type(key) != TupleType: key = ( key, ) 465 primary_key = keys.primary_key(dbclass) 466 467 if len(key) != len(primary_key.key_attributes): 468 msg = "The primary key for %s must have %i elements." % \ 469 ( repr(dbclass), len(primary_key.key_attributes), ) 470 raise IllegalPrimaryKey(msg) 471 472 info = stupid_dict() 473 for property, value in zip(primary_key.attributes(), key): 474 info[property.column] = value 475 476 return dbclass.__from_result__(self, info)
477
478 - def insert(self, dbobj, dont_select=False):
479 """ 480 @param dbobj: The dbobj to be inserted (must not be created by a 481 select statement. 482 @param dont_select: Do not perform a SELECT query for those columns 483 whoes values are provided by the backend, either through 484 AUTO_INCREMENT mechanisms or default column values. 485 """ 486 if dbobj.__is_stored__(): 487 raise ObjectAlreadyInserted(repr(dbobj)) 488 489 sql_columns = [] 490 sql_values = [] 491 for property in dbobj.__dbproperties__(): 492 if property.isset(dbobj) and \ 493 property.column not in sql_columns and \ 494 property.sql_literal(dbobj) is not None: 495 sql_columns.append(property.column) 496 sql_values.append(property.sql_literal(dbobj)) 497 498 if len(sql_columns) == 0: 499 raise DBObjContainsNoData("Please set at least one of the attributes of this dbobj") 500 501 statement = sql.insert(dbobj.__relation__, sql_columns, sql_values) 502 self.execute(statement, modify=True) 503 504 dbobj.__insert__(self) 505 506 if not dont_select: 507 self.select_after_insert(dbobj)
508
509 - def select_after_insert(self, dbobj):
510 """ 511 This method will be run after each INSERT statement automaticaly 512 generated by a ds to pick up default values and primary keys set 513 by the backend. See insert(). 514 """ 515 properties = [] 516 columns = [] 517 for property in dbobj.__dbproperties__(): 518 if property.__select_after_insert__(dbobj): 519 properties.append(property) 520 columns.append(property.column) 521 522 if len(properties) > 0: 523 where = self.select_after_insert_where(dbobj) 524 query = sql.select(columns, dbobj.__relation__, where) 525 526 cursor = self.execute(query) 527 tpl = cursor.fetchone() 528 529 for property, value in zip(properties, tpl): 530 property.__set_from_result__(self, dbobj, value)
531
532 - def select_after_insert_where(self, dbobj):
533 raise NotImplemented()
534
535 - def update(self, relation, column, sql_literal, where, ):
536 """ 537 Updates are stored in a list and executed on calls to commit() or to 538 flush_updates() to join updates to the same row into a single SQL 539 command. 540 541 @param relation: The relation to be updated 542 @param column: sql.column Name of the column to be updated 543 @param sql_literal: sql literal of the value to be stored. 544 @param where: where clause that would select (or update in this case) 545 the desired row from relation 546 """ 547 key = ( relation, where, ) 548 data = self._updates.get(key, stupid_dict()) 549 data[column] = sql_literal 550 self._updates[key] = data
551
552 - def flush_updates(self):
553 """ 554 Execute the updates stored by the update() method (see above). 555 """ 556 for key, data_dict in self._updates.items(): 557 relation, where = key 558 update = sql.update(relation, where, data_dict) 559 560 self.execute(update, modify=True) 561 562 self._updates.clear()
563
564 - def close(self):
565 self._dbconn().close()
566
567 - def delete_by_primary_key(self, dbclass, primary_key_value):
568 where = self.primary_key_where(dbclass, primary_key_value) 569 command = sql.delete(dbclass.__relation__, where) 570 self.execute(command, True)
571 572 573 # Local variables: 574 # mode: python 575 # ispell-local-dictionary: "english" 576 # End: 577