21.3. Object-Relational Managers (ORMs)As seen in the previous section, a variety of different database systems are available today, and most of them have Python interfaces to allow you to harness their power. The only drawback to those systems is the need to know SQL. If you are a programmer who feels more comfortable with manipulating Python objects instead of SQL queries, yet still want to use a relational database as your data backend, then you are a great candidate to be a user of ORMs. 21.3.1. Think Objects, Not SQLCreators of these systems have abstracted away much of the pure SQL layer and implemented objects in Python that you can manipulate to accomplish the same tasks without having to generate the required lines of SQL. Some systems allow for more flexibility if you do have to slip in a few lines of SQL, but for the most part, you can avoid almost all the general SQL required. Database tables are magically converted to Python classes with columns and features as attributes and methods responsible for database operations. Setting up your application to an ORM is somewhat similar to that of a standard database adapter. Because of the amount of work that ORMs perform on your behalf, some things are actually more complex or require more lines of code than using an adapter directly. Hopefully, the gains you achieve in productivity make up for a little bit of extra work. 21.3.2. Python and ORMsThe most well-known Python ORMs today are SQLAlchemy and SQLObject. We will give you examples of SQLAlchemy and SQLObject because the systems are somewhat disparate due to different philosophies, but once you figure these out, moving on to other ORMs is much simpler. Some other Python ORMs include PyDO/PyDO2, PDO, Dejavu, PDO, Durus, QLime, and ForgetSQL. Larger Web-based systems can also have their own ORM component, i.e., WebWare MiddleKit and Django's Database API. Note that "well-known" does not mean "best for your application." Although these others were not included in our discussion, that does not mean that they would not be right for your application. 21.3.3. Employee Role Database ExampleWe will port our user shuffle application ushuffle_db.py to both SQLAlchemy and SQLObject below. MySQL will be the backend database server for both. You will note that we implement these as classes because there is more of an object "feel" to using ORMs as opposed to using raw SQL in a database adapter. Both examples import the set of NAMES and the random name chooser from ushuffle_db.py. This is to avoid copying-and-pasting the same code everywhere as code reuse is a good thing. SQLAlchemyWe start with SQLAlchemy because its interface is somewhat closer to SQL than SQLObject's interface. SQLAlchemy abstracts really well to the object world but does give you more flexibility in issuing SQL if you have to. You will find both of these ORMs (Examples 21.2 and 21.3) very similar in terms of setup and access, as well as being of similar size, and both shorter than ushuffle_db.py (including the sharing of the names list and generator used to randomly iterate through that list). Example 21.2. SQLAlchemy ORM Example (ushuffle_sa.py)
Example 21.3. SQLObject ORM Example (ushuffle_so.py)
Line-by-Line ExplanationLines 110As expected, we begin with module imports and constants. We follow the suggested style guideline of importing Python Standard Library modules first, followed by third-party or external modules, and finally, local modules to our application. The constants should be fairly self-explanatory. Lines 1231The constructor for our class, like ushuffle_db.connect(), does everything it can to make sure that there is a database available and returns a connection to it (lines 18-31). This is the only place you will see real SQL, as such activity is typically an operational task, not application-oriented. Lines 3344The TRy-except clause (lines 33-40) is used to reload an existing table or make a new one if it does not exist yet. Finally, we attach the relevant objects to our instance. Lines 4670These next four methods represent the core database functionality of table creation (lines 46-52), insertion (lines 54-57), update (lines 59-64), and deletion (lines 66-70). We should also have a method for dropping the table: def drop(self): self.users.drop() or drop = lambda self: self.users.drop() However, we made a decision to give another demonstration of delegation (as introduced in Chapter 13, Object-Oriented Programming). Delegation is where missing functionality (method call) is passed to another object in our instance which has it. See the explanation of lines 79-80. Lines 7277The responsibility of displaying proper output to the screen belongs to the dbDump() method. It extracts the rows from the database and pretty-prints the data just like its equivalent in ushuffle_db.py. In fact, they are nearly identical. Lines 7980We deliberately avoided creating a drop() method for the table since it would just call the table's drop method anyway. Also, there is no added functionality, so why create yet another function to have to maintain? The __getattr__() special method is called whenever an attribute lookup fails. If our object calls orm.drop() and finds no such method, getattr (orm, 'drop') is invoked. When that happens, __getattr__() is called and delegates the attribute name to self.users. The interpreter will find that self.users has a drop attribute and pass that method call to it: self. users.drop()! Lines 8284The last method is finish(), which commits the transaction. Lines 86114The main() function drives our application. It creates a MySQLAlchemy object and uses that for all database operations. The script is the same as for our original application, ushuffle_db.py. You will notice that the database parameter db is optional and does not serve any purpose here in ushuffle_sa.py or the upcoming SQLobject version ushuffle_so.py. This is a placeholder for you to add support for other RDBMSs in these applications (see Exercises at the end of the chapter). Upon running this script, you may get output that looks like this: $ ushuffle_sa.py *** Connecting to 'test' database *** Creating users table *** Inserting names into table LOGIN USERID PROJ# Serena 7003 4 Faye 6812 4 Leslie 7808 3 Ernie 7410 1 Dave 7306 2 Melissa 8602 1 Amy 7209 3 Angela 7603 4 Jess 7912 2 Larry 7311 1 Jim 7512 2 Davina 7902 3 Stan 7607 4 Pat 7711 2 Aaron 8312 2 Elliot 7911 3 *** Randomly moving folks from one group (1) to another (3) (3 users moved) LOGIN USERID PROJ# Serena 7003 4 Faye 6812 4 Leslie 7808 3 Ernie 7410 3 Dave 7306 2 Melissa 8602 3 Amy 7209 3 Angela 7603 4 Jess 7912 2 Larry 7311 3 Jim 7512 2 Davina 7902 3 Stan 7607 4 Pat 7711 2 Aaron 8312 2 Elliot 7911 3 *** Randomly choosing group (2) to delete (5 users removed) LOGIN USERID PROJ# Serena 7003 4 Faye 6812 4 Leslie 7808 3 Ernie 7410 3 Melissa 8602 3 Amy 7209 3 Angela 7603 4 Larry 7311 3 Davina 7902 3 Stan 7607 4 Elliot 7911 3 *** Dropping users table $ Line-by-Line ExplanationLines 110This modules imports and constant declarations are practically identical to those of ushuffle_sa.py except that we are using SQLObject instead of SQLAlchemy. Lines 1242The constructor for our class does everything it can to make sure that there is a database available and returns a connection to it, just like our SQLAlchemy example. Similarly, this is the only place you will see real SQL. Our application, as coded here, will result in an infinite loop if for some reason a Users table cannot be created in SQLObject. We are trying to be clever in handling errors by fixing the problem and retrying the table (re)create. Since SQLobject uses metaclasses, we know that special magic is happening under the covers, so we have to define two different classesone for if the table already exists and another if it does not. The code works something like this:
Hopefully it (eventually) succeeds in one of the first two places. When the loop is terminated, we attach the relevant objects to our instance as we did in ushuffle_sa.py. Lines 4467, 7778The database operations happen in these lines. We have table create (lines 44-47) and drop (line 77), insert (lines 49-52), update (lines 54-60), and delete (lines 62-67). The finish() method on line 78 is to close the connection. We could not use delegation for table drop like we did for the SQLAlchemy example because the would-be delegated method for it is called dropTable() not drop(). Lines 6975This is the same and expected dbDump() method, which pulls the rows from the database and displays things nicely to the screen. Lines 80108This is the main() function again. It works just like the one in ushuffle_sa.py. Also, the db argument to the constructor is a placeholder for you to add support for other RDBMSs in these applications (see Exercises at the end of the chapter). Here is what your output may look like if you run this script: $ ushuffle_so.py *** Connecting to 'test' database *** Creating users table *** Inserting names into table LOGIN USERID PROJ# Jess 7912 1 Amy 7209 4 Melissa 8602 2 Dave 7306 4 Angela 7603 4 Serena 7003 2 Aaron 8312 1 Leslie 7808 1 Stan 7607 3 Pat 7711 3 Jim 7512 4 Larry 7311 3 Ernie 7410 2 Faye 6812 4 Davina 7902 1 Elliot 7911 4 *** Randomly moving folks from one group (2) to another (3) (3 users moved) LOGIN USERID PROJ# Jess 7912 1 Amy 7209 4 Melissa 8602 3 Dave 7306 4 Angela 7603 4 Serena 7003 3 Aaron 8312 1 Leslie 7808 1 Stan 7607 3 Pat 7711 3 Jim 7512 4 Larry 7311 3 Ernie 7410 3 Faye 6812 4 Davina 7902 1 Elliot 7911 4 *** Randomly choosing group (3) to delete (6 users removed) LOGIN USERID PROJ# Jess 7912 1 Amy 7209 4 Dave 7306 4 Angela 7603 4 Aaron 8312 1 Leslie 7808 1 Jim 7512 4 Faye 6812 4 Davina 7902 1 Elliot 7911 4 *** Dropping users table $ 21.3.4. SummaryWe hope that we have provided you with a good introduction to using relational databases with Python. When your application's needs go beyond those offered by plain files, or specialized files like DBM, pickled, etc., you have many options. There are a good number of RDBMSs out there, not to mention one completely implemented in Python, freeing one from having to install, maintain, or administer a real database system. Below, you will find information on many of the Python adapters plus database and ORM systems out there. We also suggest checking out the DB-SIG pages as well as the Web pages and mailing lists of all systems of interest. Like all other areas of software development, Python makes things easy to learn and simple to experiment with. |