![]() |
A SQL Database <--> Python <--> XML SolutionJ. Michael Caine Authored: 4/8/2003, J. Michael Caine |
Introduction
Abstract
A Preview Snapshot
What's Included Here
Time to Mastery
Prerequisites
Goals
Non-Goals
Related Works
Conventions and Limitations
Motivation
DB -> Python-objects
Python-object -> XML
XML -> Python-object
Python-object -> DB
A Complete Example
Performance
Conclusion
Collection, alteration, and presentation of data by multi-tier, networked systems is a common problem, and many solution options exist. Presented here is one solution approach which fits a large subset of needs and has many advantages.
The approach is:
A convenient path from an existing SQL DB --> application logic --> XML, for client-side presentation (e.g. in HTML), and back again, is presented.
A database of community libraries contains tables like this:
book:
genre:
library:
possessions:
It would be convenient to access this data in Python like this:
>>> print book.title Hard Times >>> print book.genre.description Works of general fiction >>> print book.ownerLibrary[0].name Alameda County
Furthermore, this mechanism fits the "path" paradigm, common to XML path (XPath) specification, among other things. It would be convenient to issue this command:
>>> Xml.Dump(book)
to generate this:
<book>
<title> Hard Times </title>
<pageCount> 838 </pageCount>
<genre>
<description> Works of general fiction </description>
</genre>
<!-- etc. -->
</book>
Of equal convenience, of course, would be the converse: a Parse() call which generates the Python object illustrated above.
There are many considerations to investigate.
Accompanying this discussion you will find the complete source for the DbXml python module, an implementation of the features outlined in this paper. Other implementations abound, of course. This implementation is Copyright 2003 Art & Logic, Inc., but is free for use and abuse under !!! license. This module's classes each have extensive unit testing code which can be rather helpful for understanding the usage details. When in doubt, look to the examples at the bottoms of these files, in the UnitTest() functions.
You'll also find a complete but short "real-world" example of usage implementing a time-tracker. (See A Complete Example for more information.)
An associated (not yet complete) whitepaper investigates the Python, SQL/DB, and XML considerations made in the accompanying DbXml python module in more detail. It treats, for example, Unicode handling, quoting and escaping, exception handling, Python tricks, DB-synchronization and "smart-merging" with other asynchronous transactions, lock/unlock and commit/rollback semantics, and more.
Depending on one's level of familiarity with an RDBS, Python, and XML, absorbing this discussion and starting a real project could take anywhere from one to three hours.
Absorbing the associated whitepaper would take another hour or two, but it is unnecessary for proficient use of the accompanying DbXml module.
Python, an RDBMS, and a SQL interface between the two must be obtained to implement the solution presented here. All code presented here was tested on a Windows 2000 system using Python 2.2, MySQL 3.22, and the MySQLdb 0.9.2 Python module (DB-API-v2.0-compliant). These can be obtained, respectively, at:
Some familiarity with Python, SQL, and an RDBMS is necessary. Other RDBMS solutions and DB-API-compliant Python/SQL interfaces should work, but have not been tested.
It is the goal of this discussion to reveal an efficient method for reliably accessing, altering, and presenting data in widely-accepted formats (using a relational database, Python, and XML, respectively).
Fundamental treatment of Python, XML, and RDBMSs will not be included. Refer to the following tutorials for background:
The following topics will not be treated, beyond reference, in this discussion.
Significant traffic on these topics can be found on the web. The following are promising and noteworthy related works. Please advise (mcaine@artlogic.com) if other candidates are found.
"XML databases" - http://www.sdmagazine.com/documents/s=819/sdm0302e/
(found in the February 2003 Software Development magazine)
Rick Wayne weighs in on the world of XML database technology, comparing the major players
(IBM, Oracle, Microsoft, Apache) and smaller fries and their XML DB tacks and tools.
This and similar discussions treat the effort to expose an XML interface to a database,
directly, as opposed to the DbXml module we discuss in this document, which introduces
Python as a complete middle-tier application-logic language.
"object prevalence" - http://www-106.ibm.com/developerworks/library/wa-objprev/
This is a provocative solution to the mismatch between the now-pervasive object-oriented
paradigm and data persistence, as we know it. It's not your usual object-database (as opposed
to relational-database) discussion; instead, it proposes a mechanism of constant, deterministic
serialization that is founded on simplicity and implemented in Java.
"Gadfly" - http://gadfly.sourceforge.net/gadfly.html#what-is-it
This is a Python-integrated RDBMS that removes the need for a SQL translation layer between
Python code and an existing, general RDBMS. It is convenient and promising, but lacks true
concurrency control and other features necessary for large database solutions. Because it is
SQL- and Python-Database-API (Greg Stein) compliant, it is completely interoperable with the
solution discussed here.
"Object-Relational Bridge for Python" - http://modeling.sourceforge.net/
Entity-Relationship Modeling, familiar to most relational database buffs, can be used to link
an entire database schema to a Python class structure. Many RDBMS-to-middleware-language solutions
rely on such a design-time setup (e.g., WebObjects' Enterprise Object Framework). Alternatively,
we propose in this document a mechanism for specifying similar "mini-schema", on the
fly, for right-sized subsystems that don't require entire database synchronization or design-time
class-definition generation.
A number of discussions about XML and Python objects are interesting. For instance, David Mertz and others participate in this discussion here, which treats the topic with references to Fredrik Lundh's work (http://effbot.org/downloads/), Gnosis (http://gnosis.cx/publish/programming/xml_matters_23.html and others), etc.
Formal limitations and conventions will be avoided so that the model may be extended or adapted as easily as possible. One convention that cannot be avoided is this:
(Other) conventions may be formally developed, for a given solution, such as, in the form of a DTD, and such conventions may enhance the performance of certain operations.
For the purposes of this discussion, we will only consider relatively "simple", but useful, XML and database relationships, as shown in the Motivation section below. We won't consider formal foreign keys in the DB (some DBMS's philosophically disagree with foreign keys and cascading operations anyway) or XML namespaces, for instance.
The Goals section referred to "widely-accepted formats". The key to a solution such as this is versatility and familiarity. Most engineers in this domain are familiar with the general concepts of relational data organization. An ideal solution capitalizes on this familiarity.
For many applications (or web applications), the application logic itself makes direct use of SQL-level access to perform operations efficiently or preserve data integrity. In many other cases, the application logic would prefer to work with data in the application language rather than SQL constructs. This document treats this latter set of cases.
To demonstrate, let's consider calculating the total pages from the books in the 'book' table illustrated above. A single SQL statement could be constructed to do this, but let's consider this task representative of more difficult calculations which would require more than what you get with built-in SQL functions. In Python (a common middle-tier language) and raw SQL, one might do it this way:
cursor = sqlDb.cursor()
cursor.execute('SELECT pageCount FROM book')
fields = cursor.fetchall()
for field in fields:
totalPages += fields[0]
This approach is common to all languages' SQL interfaces, and the Python implementation is already rather simple, especially when dealing with one table or one SELECT resultset and only a few fields. However, the need to process bulkier requests is more common, and accessing the data captured from the 'book' table would be more easily and legibly accomplished using Python data structures (classes):
db = Db(sqlDb) books = db.Get(table = 'book') # fields = ['pageCount'] is optional for book in books: totalPages += book.pageCount
Since the structure of the book table is known (that is, the field named "totalPages" is in the table named "book"), this is more legible and more convenient than accessing fields[0] directly or manually assigning the fields to reasonable names for every resultset.
Consider for a moment a more complex resultset:
SELECT book.* FROM book, library, possessions WHERE possessions.ownerLibrary = library.id AND possessions.book = book.id AND library.name = "Alameda County";
(Note that 'possessions' is a join-table identifying which libraries possess which books). In other words, visually:

In this case, it would be nice to be able to access captured data in the same convenient way (spelled out in unnecessary detail here for clarity):
possessions = TableJoiner( \
joinTableName = 'possessions', \
leftIdFieldName = 'ownerLibrary', \
rightIdFieldName = 'book', \
rightTableName = 'book')
libraries = db.Get('library', [possessions], where = 'library.name = "Alameda County"')
for library in libraries: # consider each library (only one in this case, actually)
for book in library.book: # consider each book in that library
totalPageCount += book.pageCount
Though a more detailed explanation of the design, API, and implementation will be given, this example should be mostly self-explanatory.
Application logic languages, such as Python, are most adept at performing calculations on data and making application decisions based on data values, whereas languages such as SQL are most adept at storing and retrieving data. This motivates a solution that glues the two together as demonstrated here.
Now that we've established motivation for a DB ( Python-object link, we must establish a motivation for a Python-object ( XML link.
XML is now the common language for information dissemination. Especially when combined with XSL transform technologies. If, after massaging the data and/or making application decisions in Python, you wish to deliver the above data to a client, you'll probably want it to look something like this:
<library>
<name> Alameda County </name>
<book>
<author> Charles Dickens </author>
<pageCount> 673 </pageCount>
</book>
<book>
<author> Dietrich Bonhoeffer </author>
<pageCount> 341 </pageCount>
</book>
</library>
And this may eventually be presented in any number of HTML formats via various transforms.
Of course, especially considering B2B/P2P technologies that shuffle data packaged in XML, the motivation for developing the reverse solution (XML --> Python-dictionary --> DB) is self-evident.
First, consider simple DB tables. The effort to load a resultset of records into a python dictionary list is straightforward:
records = cursor.fetchall()
for record in records:
dict = {}
for field in range(len(record)):
dict[fieldNames[field]] = record[field]
list.append(dict)
These dictionary lists, or "trees", can be converted into standard Python objects by clever use of the Python __getattr__() and __setattr__() methods. An implementation of this can be found in the form of the Node class. Consider the following tree, represented as a dictionary:
{"book": \
{"author": "Charles Dickens", \
"title": "Hard Times", \
"genre": \
{"name": "Fiction", \
"description: "Works of general fiction"}}}
This could be represented as a Node, so that access looks like this:
x.book.genre.name
rather than this:
x['book']['genre']['name']
On this foundation, let's consider the DB interface. First of all, a DB-API-v2.0-compliant DB (ODBC) module will be required. One such module for MySQL databases is MySQLdb:
import MySQLdb sqlDb = MySQLdb.connect(db='LibraryCatalog', user='test', passwd='test')
Creating a DbXml "Db" object is easy:
from Core.DbXml.Db import Db db = Db(sqlDb)
Using this object to retrieve fields in a table is also straightforward:
books = db.Get(table = 'book') books[0].title # this would return "Hard Times", e.g.
An implementation of this can be completed in a few lines of code using the mechanisms described above. The implementation in the DbXml module is more complex, of course, because we want to be able to do much more than load the simple field values of single tables.
At this point, we will abandon much discussion of implementation details (consider the associated whitepaper for such details) and will focus on understanding the usage of Db.Get().
Consider the opening example in the Motivation section, where we were concerned only with retrieving the pageCount field. To simplify or enhance the query efficiency, we could specify the exact field(s) we want. Let us assume that the pageCount and the genre were the only important fields:
books = db.Get(table = 'book', fields = ('pageCount', 'genre'))
This would result in a collection of books with these fields only.
books[0].genre # this might return the number 1, e.g. books[0].author # this would raise an AttributeError exception
Now let's assume that a genre of "1" or "2" is really useless information. In the database, we know that this is simply an id field pointing (a la "foreign key") to the 'Genre' table, identifying one of the records in that table. This is a 1-to-1 relationship, very common in databases, and it can be represented in terms of the DbXml implementation as a "DirectJoiner":
bookGenre = DirectJoiner( \ joinFieldName='genre', \ joinedTableName='Genre')
This instructs the Get() operation to fetch a record in the 'Genre' table when it encounters a value in the 'genre' field of the 'Book' table (the record to fetch is implicitly identified by the unique 'id' in the 'Genre' table, based on our assumption that all data tables have an 'id' field for unique record identification). The modified operation would look like this:
books = db.Get('Book', ('pageCount', bookGenre))
(Notice that we've begun referring to tables with capital letters, such as 'Book' and 'Genre'. Typically, RDBMSs are case insensitive, and SQL requires explicit quotes to enforce case-sensitivity for case-aware RDBMSs anyway. Thus, to help us distinguish between fields and tables, we'll capitalize table names, when sent as parameters, from here on.)
In this case, the following would all be legal:
books[0].pageCount books[0].genre.name books[0].genre.description
Note that the joinFieldName (in the database) is used; that is, 'genre', not the variable-name 'bookGenre' or the table name (which happens to be 'genre', or 'Genre', in this case).
If only the genre.name was desired, then the fieldsInJoinedTable argument could have been used in the DirectJoiner construction in the same way that the fields argument is used in Get(). Thus, rather than this shortened form of the statement above:
bookGenre = DirectJoiner('genre', 'Genre')
we'd have this:
bookGenre = DirectJoiner('genre', 'Genre', ['name'])
It's also worth noting that the "id" field is always retrieved, so, even for this last example,
books = db.Get('Book', ('pageCount', bookGenre))
books[0].id
returns the value of the id field in the DB for that record. This id field must remain untouched if you wish to submit alterations to the DB record later (using Set()).
This DirectJoiner class accomplishes one of the main objectives of good database design, since many books belong to the same genre, and the genre information need only be entered once in the DB. However, when working with the data in an application logic language like Python, you may indeed want direct access to the genre of a given book, as illustrated above. Take caution that changes (for example) to the genre data, through a book Node, will result in changes to the single underlying genre data structure in the DB!
There is nothing magical about this realization of 1-to-1 relationships. As you can see in DbImp.py, when a DirectJoiner instance is encountered while the field list is being traversed in Get(), the joinFieldName and joinedTableName are discovered. These are used to construct the appropriate SQL command to select the record in the joined table whose id is equal to the reference id in the record under consideration.
command = ('select %s from %s, %s where %s.id = "%s" and ' \
+ '%s.%s = %s.id') \
% (flatFields, table, joinedTable, table, node.id, \
table, joinField, joinedTable)
Get() is then called recursively on this linked-in table and record, establishing the class-representation of the data in the 1-to-1 relationship.
N-to-N relationships also abound in database designs. For example, as shown above:

This establishes that one library may possess many books and that one book may be possessed by many libraries (by "book", here, we mean a book specification (title, copyright, etc.), not a physical book instance, which can only be in one place at a time, of course).
In (Python) code, it is most convenient to "view" such relationships from one perspective or another. In other words, I may want to iterate through the N books in a given library, or, alternatively, I may want to iterate through the N libraries that may have a given book. Thus, in application logic, we prefer to consider 1-N relationships for a given operation. One implementation that enables this is the TableJoiner class, a brother of the DirectJoiner class:
possessions = TableJoiner( \ joinTableName = 'Possessions', \ leftIdFieldName = 'ownerLibrary', \ rightIdFieldName = 'book', \ rightTableName = 'Book')
Ultimately, to iterate through all of the books possessed by given libraries:
for library in libraries:
for book in books:
# do whatever
Conversely, we could have assembled the possessions object with the 'book' on the left and 'ownerLibrary' on the right. Then we could iterate through books, then libraries, to see all of the libraries that possess a given book.
This example is found in the Motivation section, and won't be belabored here.
Note, again, that the 'joinFieldName' argument in a DirectJoiner construction specifies the resulting Node object's attribute name. Likewise, the 'rightIdFieldName' argument in a TableJoiner construction specifies the resulting Node object's attribute name.
When joiner objects (DirectJoiner and TableJoiner objects, that is) are all alone in the 'fields' list of a Get() operation or another Joiner object constructor, the "flat" fields are still retrieved for the given table. For example:
db.Get('Book', (bookGenre, libraries))
will still return the book's author, title, etc. But:
db.Get('Book', (bookGenre, libraries, 'pageCount'))
will not - it will only return the pageCount (and id) values, in addition to the linked tables.
Joiner objects can be linked ad infinitum to accomplish "deep" Get() operations. Here is a more complex example:
address = DirectJoiner('address', 'Address')
issuingBank = DirectJoiner('issuingBank', 'Bank', [address])
company = DirectJoiner('company', 'Company', [address])
creditCard = TableJoiner('CreditCards', 'holder', 'card', 'CreditCard',
[company, issuingBank])
clients = db.Get('Client', ('name', address, creditCard)
This will retrieve the clients' names, addresses, and credit cards so that they can be accessed as follows:
clients[0].name = "Charlie"
for card in clients[0].card
if "Visa" == card.company.name:
# he's a Visa holder...
(We will leave the raw SQL equivalent as an exercise.)
For the above example, considering the whole database, hypothetically, the "address" table may be the right-hand-side of an N-to-1 relationship where certain people and or companies may have the same address. Yet each or some may be joined in a DirectJoiner way, and indeed this is what is used twice as shown above. Likewise, one client may have many credit cards, implying a 1-to-N design for that portion of the database; most likely many clients will not share the same credit card (number, for instance), though their credit card company may be identical. A join-table is used, and TableJoiner is used to discover the many cards of one client - an attempt to discover the many clients holding a given credit card may not be useful, or it may be useful for spouses who hold a card together (for instance).
Now consider the need to present this data or send it to a processor of unknown platform, language, etc. Typically, these goals are achieved by representing the data in XML, perhaps to ultimately be transformed into HTML or slightly-different XML according to some DTD specification for some other system that needs the data, often using XSLT. In any event, representing the data itself in XML is valuable enough that many DBMSs have been investing significantly in "XML database" technologies and now offer XQuery, DB-XPath, etc., to integrate XML and the DB backend directly (see http://www.sdmagazine.com/documents/s=819/sdm0302e/).
Frequently, having a powerful language between the raw data and an XML representation is useful. Python is the powerful language presented in this document, and, indeed, as shown, it sits between the classical RDBMS and XML. Generating XML using DbXml.Xml is straightforward:
books = db.Get('Book', [bookGenre])
from DbXml import Xml
Xml.Dump(books[0])
This will generate XML that looks like this:
<book>
<title> Hard Times </title>
<printed> 1960-05-10 </printed>
<genre>
<name> Fiction </name>
<description> Works of general fiction </description>
</genre>
<!-- etc. -->
</book>
The reverse translation, XML --> Python-object, is nearly as straightforward. Here, we want to consider more complex XML which may come from some source over which we have no control. XSLT could always be written to transform such source XML into a format preferred by our XML --> Python-object parser, but this may be an undesirable step. Instead, let's consider a few noteworthy items.
Xml.Parse() parses "data XML" and returns a Node object; it is the converse operation of Xml.Dump(). By "data XML", we mean that each XML node typically contains either more XML nodes or an atomic data value.
>>> node = Xml.Parse('<y> <z> 3 </z> </y>')
>>> print node.y.z + 1
4
Note that whitespace is stripped and that attempts are made to interpret values according to their most likely types (see XmlImp's ConvertToLikliestType). Multiple tags of the same name are grouped into a list:
>>> node = Xml.Parse('<y> <z> 3 </z> <z> three </z> </y>')
>>> print node.y.z[0]
3
>>> print node.y.z[1]
three
However, some well-formed XML cannot really be considered good "data XML". For instance: <A> hello <E> 3 </E> goodbye </A>. Five rules should be observed:
To demonstrate rules two and four, consider:
<A>
hello
<B> </B>
<C> Value </C>
<D attr="value"> </D>
goodbye
</A>
The resulting node looks like this:
>>> node.A.A1 hello >>> node.A.A2 goodbye >>> node.B None >>> node.C Value >>> node.D.attr value
Note that if Dump() was used to dump this node back into XML, the XML would not mirror the original XML. The exact structure is unimportant, of course, for data XML, but it may be important if an XML format is expected, for instance, by a third-party. The Xml and Node implementations would have to be extended to preserve all the information necessary to reproduce the original XML exactly. There is little value to doing this.
Python's xml.dom.minidom currently considers text nodes Unicode by default. This may be inconvenient as ASCII is often sufficient for DB data. Set 'downcastUnicode' to 1 in order to convert Unicode text elements to ASCII strings.
The XML parsing/dumping code is rather simple. Most of the work is done by xml.dom.minidom, which is sufficient for our purposes. SAX/expat or other parsers are available if a richer feature set is needed.
It's worth reiterating the value of our "data XML" assumptions, and capitalizing on the beauty of XML. If you wanted to add a new book to our "books" table in the database, and all the information you had was the book's title and author, and, furthermore, you were remote and had to submit your addition via the ubiquitous XML, you might submit something like this:
<book> <title> Circuits </title> <author> Bruce Carlson </author> </book>
The order in which you presented the title and author wouldn't matter, and it wouldn't matter that you left information out. At the server, Xml.Parse() would convert this into a Node object which could easily be inserted into the database.
Later, you may want to modify this record. Presumably, you obtained the 'id' of the book (the only way of really identifying it uniquely). You now know the genre to which the book belongs, so you submit something like this:
<book> <id> 3 </id>
<genre>
<name>College Texts </name>
</genre>
</book>
This new genre will be "automatically" added to the database and the "Circuits" book will be the first linked to it. Of course, the genre information, and/or any new or existing libraries possessing this book, for that matter, could have been included in the original <book> XML to accomplish the same "composite, automatic" DB update while adding the new book itself.
In the last section we mentioned the ability to get data represented in XML into the actual database. Db.Set(), the converse of Db.Get(), is the mechanism for doing this. Here is an example:
newBook = Node.Node() newBook.author = 'A. Bruce Carlson' newBook.title = 'Circuits' db.Set(table='Book', records=newBook)
Note that book.id is not manually set! This results in the addition of the book. If an id was set and that id already existed in the DB, then the corresponding record in the DB would be updated with the given information. It is very important here that the 'id' fields in the DB tables are always created 'UNIQUE' (typically, when creating the table, the unique primary key field should be set and named 'id' - consult the SQL specification or your RDBMS help for more detailed instructions).
Once Set() has been called (e.g., to add a record, as above), the record acquires its id value, and modifications can be made as such:
newBook.pageCount = 838
db.Set('Book', newBook)
Though there was no need to Get() the record again in the above example, typical record modification will consist of a Get(), the modification, then a Set():
books = db.Get('Book') # gets all books
# modify here...
db.Set('Book', books) # sets all books
Here, since no books were modified, the operation is a quick no-op. Any modifications made to any of the books in the list would be updated in the DB with the call to Set(). Any books (Node objects with compatible fields) appended to the list would be added anew to the DB.
Let's consider a more complex scenario. Recall the newBook variable set up above with Carlson's book:
newBook.genre = Node()
newBook.genre.name = 'College Texts'
coconinoLibrary = Node({'name': 'Coconino County'})
klamathLibrary = db.Get('Library', (), 'name = "Klamath County"')
newBook.ownerLibrary = (coconinoLibrary, klamathLibrary)
db.Set('Book', newBook)
This results in the modification (or addition, if the book wasn't added above) of the new book, the addition of the new genre, "College Texts", the addition of the new library, "Coconino County", and the association of this book with the new genre, new library, and the old "Klamath County" library. This may seem a rather unusual scenario, and for the given DB or software or software team, it may be "against the rules", but the same general form may be perfectly suitable for another DB.
The previous example adequately illustrates a number of concepts which, at this point, we needn't belabor. Therefore, let's consider one last short example - a full, albeit straightforward, round trip scenario:
books = db.Get('Book') # gets all books (no genres, etc.)
x = Node({'doc': {'book': None}})
x.doc.book = books # wrap all the books in a doc tag
Xml.Dump(x) # dumps all books into XML: <doc> ... </doc>
At this point, we ship the XML off to another application, machine, country, website form, or whatever, and it comes back looking like this (hypothetically):
<reply> <book> ... </book> <book> ... </book> ... </reply>
That is, where the books may have a bunch of sundry modifications. If the above string is assigned to a variable named reply, then:
y = Xml.Parse(reply)
db.Set('Book', y.reply)
That's it. Of course, this can easily be scaled up to include joined tables, as discussed earlier in this document. It may also be wise to validate the reply XML, e.g., via DTD and a validating parser (xml.dom.minidom is not a validating parser).
The TimeTrackerImp.py example demonstrates usage of the DbXml implementation presented here. It demonstrates:
To complete the analysis of this approach, performance must be considered.
The performance of the DB ( Python-object mechanism is the most interesting and lends itself to a simple technique for analyzing performance. The unit test code was run after turning on traceouts of all of the SQL commands. Then the unit test itself was clocked against the execution of those raw SQL commands, thus revealing the time spent managing the DB ( Python-object mechanism.
This actual test we ran reveals results only for a special case: a very small database (our test database) and the most complex facilities of the Db and DbImp classes (as tested in the unit test code). That is, it significantly exercises the Db and DbImp classes and under-exercises the DB/DB-interface. Since these are inextricably related, a more complete test would investigate large complex databases and simple Db/DbImp features and compare the results.
Nevertheless, for this worst-case scenario, the Db/DbImp classes took almost twice as long, in debug mode, as the raw SQL statements themselves. One way of putting this is that "figuring out" the SQL commands and translating data to and from DbXml.Node objects takes almost as much time as executing the SQL commands themselves, when they are extremely simple to execute.
The performance of the XML ( Python-object mechanism is less easily tested, for there is no known or canonical reference point for comparison. Refer to the UnitTest module's TimeTest mechanism to get an idea for the amount of time spent on these operations. The mechanism is straightforward and the unit-test times seem reasonable.
One solution to the common problems involved with collection, alteration, and presentation of data by multi-tier, networked systems was proposed. An efficient method for reliably accessing, altering, and presenting data in widely-accepted formats (namely, SQL, Python, and XML) was discussed. The approach demonstrated the ability to solve these common problems inexpensively, tersely, platform-independently, and with very few technologies.
A convenient path from a SQL DB --> application logic --> XML, for client-side presentation (e.g. in HTML), and back again, was presented.