Package: dpdb
Introduction============This module provides a simple wrapper mechanism that abstracts awaydifferences in various `DB-API`_ modules.  It is compatible with both Python2.7 and Python 3.x.The Python DB-API specifies a standardized set of mechanisms to accessdifferent database engines.  However, the specification allows forconsiderable leeway in how SQL statements and queries are presented to theuser, including different parameter quoting styles.This module abstracts away those differences and also allows for SQLstatements and queries to be completely isolated from Python code.  It servesas a very lightweight database abstraction library, and makes it possibleto switch database engines by swapping out a single configuration file.Unlike many full-fledged ORM systems, it does not impose any structuralrequirements on the database itself and in fact encourages programmers towrite arbitrarily complex queries that take advantage of the database'snative ability to manipulate data.The core of the module is the Database class, which represents a connectionto a database.  Database objects are instantiated with a configuration thatdescribes how to connect to a database (what module to use and what argumentsto pass to the module's `connect` function), and what queries will be runon the database.  Configurations are just Python dictionaries (or any otherobject that inherits from the `collections.Mapping` abstract base class),and can be easily read from JSON files or `ConfigParser` objects.  Here isa simple example configuration that connects to a SQLite in-memory database:>>> config = {...     "MODULE": {...         "name": "sqlite3"...     },...     "DATABASE": {...         "database": ":memory:"...     },...     "QUERIES": {...         "create_table": "CREATE TABLE users (name TEXT NOT NULL PRIMARY KEY, password TEXT NOT NULL)",...         "create_user": "INSERT INTO users(name, password) VALUES(${name}, ${password})",...         "list_users": "SELECT * FROM users ORDER BY name ASC",...         "get_password": "SELECT password FROM users WHERE name = ${name}",...         "delete_user": "DELETE FROM users WHERE name = ${name}"...     }... }We can create a database using this configuration and create a testtable using the "create_table" query that we defined (in a real worldimplementation, the database would probably have already been populated,but this serves well for an example):>>> db = Database(config)>>> result = db.create_table()Note how the queries we've defined become methods on the Database objectwe created.We can call methods on the database object to execute queries, passingparameters in as necessary.  The parameters are automatically converted tothe module's appropriate paramter quoting mechanism:>>> result = db.create_user(name="bruce", password="iamthenight")>>> result = db.create_user(name="arthur", password="glublub")The arguments passed to the query are safely substituted into the queriesdefined in the configuration.  Queries return results as lists of rowspassed through a row factory function; by default this turns each row intoa dictionary mapping column names to values:>>> db.list_users() == [{"name": "arthur", "password": "glublub"}, {"name": "bruce", "password": "iamthenight"}]TrueA different factory function can be specified at instantiation time.It takes two arguments: a DB-API standard cursor object and a row, and canreturn any value.  For example, here is the default row factory function:>>> def row_factory(cursor, row):...     return {name[0]: value for name, value in zip(cursor.description, row)}>>> db2 = Database(config, row_factory)>>> result = db2.create_table()>>> result = db2.create_user(name="dick", password="batmanrules")>>> db2.list_users() == [{"name": "dick", "password": "batmanrules"}]TrueConnection and Transaction Contexts===================================The Database class also acts as a context manager:>>> with Database(config) as db:...     result = db.create_table()...     result = db.create_user(name="hal", password="brightestday")...     result = db.list_users()The Transaction class also provides a context manager that implementstransactions:>>> db = Database(config)>>> result = db.create_table()>>> try:...     with Transaction(db):...         result = db.create_user(name="hal", password="brightestday")...         result = db.create_user(name="hal", password="darkestnight")... except Exception:...     print("transaction failed")transaction failedAnd note that because the failure happened within a transaction, nothingwas added to the database:>>> db.list_users()[]This mechanism has the nice benefit that transactions can include non-databaserelated statements within the context that will cause an automatic transactionrollback should they fail.Unsafe Substitutions====================The "QUERIES" section of the database configuration allows parameterizationusing `string.Template` syntax.  These substitutions are automaticallyconverted to the module's native substitution format (`qmark`, `named`, etc).These substitutions can appear in arbitrarily complex queries:>>> config["QUERIES"]["update_password"] = "UPDATE users SET password = COALESCE(${password}, password) WHERE name = ${name}">>> with Database(config) as db:...     result = db.create_table()...     result = db.create_user(name="clark", password="greatcaesarsghost")...     result = db.update_password(name="clark", password="visitbeautifulkandor")...     db.list_users() == [{"name": "clark", "password": "visitbeautifulkandor"}]TrueHowever, many database engines only allow certain portions of queries to beparameterized using parameter substitution.  Often, "structural" componentsin a query (the names of tables, columns used for sorting, sort order,limits) cannot be substituted using the module's substitution mechanism.For these sorts of situations, unsafe substitution can be used.  Note thatthe name means what it says: using this form of substitution can result inSQL injection attacks, so use them wisely!Unsafe substitutions are indicated by using normal Python string interpolationsyntax.  For example:>>> config["QUERIES"]["list_users"] = "SELECT * FROM users ORDER BY name %(order)s">>> db = Database(config)>>> result = db.create_table()>>> result = db.create_user(name="ralghul", password="lazarus")>>> result = db.create_user(name="ocobblepot", password="wahwahwah")>>> db.list_users(order="DESC") == [{"name": "ralghul", "password": "lazarus"}, {"name": "ocobblepot", "password": "wahwahwah"}]True>>> db.list_users(order="ASC") == [{"name": "ocobblepot", "password": "wahwahwah"}, {"name": "ralghul", "password": "lazarus"}]TrueTesting This Module===================This module has embedded doctests that are run with the module is invokedfrom the command line.  Simply run the module directly to run the tests.Contact Information and Licensing=================================This module was written by Rob King (jking@deadpixi.com).This program is free software: you can redistribute it and/or modifyit under the terms of the GNU Lesser General Public License as published bythe Free Software Foundation, either version 3 of the License, or(at your option) any later version.This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See theGNU Lesser General Public License for more details.You should have received a copy of the GNU Lesser General Public Licensealong with this program.  If not, see <http://www.gnu.org/licenses/>.