The Dabo Book

Revision 15

Warning

This book is in the process of being written. It was begun on or about September 7, 2004. It isn't anywhere near complete, and depending on where Dabo goes during its active development push to 1.0, it may even be wrong. My intent is for the release of this book to coincide with Dabo 1.0, which should happen sometime mid-late 2005.

Paul McNett

This work is licensed under the Creative Commons Attribution License. To view a copy of this license, visit http://creativecommons.org/licenses/by/2.0/ or send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.

(Publication Date TBA)


Table of Contents

Preface
Audience
How to Read this Book
Conventions Used in This Book
Typographic Conventions
Icons
Organization of This Book
This Book is Free
Acknowledgments
I. Introduction
1. What Is Dabo?
3-Tier
Flexible Database Support
Flexible User-Interface Support
Flexible Platform Support
Summary
2. History of Dabo
3. Procuring and Installing Dabo
Downloading and Installing
Testing Your Install
4. Introductory Python
Indentation
The Python Interactive Interpreter
Exception Handling
Namespaces and Modules
Data Types
Sequence Types
Other Frequently-Used Types
Functions and Classes
Connecting To Databases
5. ??? Placeholder ???
II. Basic Concepts
6. Dabo Objects
dObject of My Affection
dObject Properties
dObject Methods
III. Tutorial: Creating a Non-Trivial Dabo Application
IV. Class Reference
V. Appendices
A. Reporting Options
ReportLab
B. Application Deployment
C. The Dabo Book Copyright
Index

List of Figures

1.1. Dabo's 3-Tiers
1.2. Supported databases.
1.3. Supported UI toolkits after 1.0
1.4. Dabo application running on Linux
1.5. Dabo application running on Macintosh
1.6. Dabo application running on Windows
6.1. All objects are based on dObject.

List of Examples

4.1. Getting Started with the Python Interpreter
4.2. The try...except block
4.3. Using try...except to handle type checking
4.4. Importing a module
4.5. A list of dictionaries representing a data table
4.6. Function and Class Definitions
4.7. Connecting to a MySQL database
6.1. Subclass of dCheckBox

Preface

(find a fitting remark or quote and put it here).

Microsoft purchased Fox Software and gained the knowledge base to produce the best user-friendly application development suites in the industry. Using Visual Basic, Access, .NET, COM, MSDE and FoxPro, hundreds of thousands of developers and consultants world-wide were able to provide solutions for their clients, from shrink-wrapped applications to completely custom programs.

For over a decade, the situation was mutually beneficial: the large company got to make billions off its operating system, and the developer got to make a living selling solutions using low-priced development tools provided by that same large company.

Something started happening, however, late in the 1990's. At a time when everyone thought Unix was uttering its final words, "Linux" became a household term. A student's hobby in 1991 became a world-class enterprise operating system by 1998, and since then Linux has made impressive gains in the user application space: the desktop. At about the same time, Apple Computer released Darwin - another Unix based operating system - to the open source world and built a rock-solid user friendly interface on top of it, and today Apple's successful future seems guaranteed: elegant, user-friendly applications built on top of Darwin/OS X, such as iTunes, iPhoto, iDVD, and iMovie are reverberating among a growing user base.

A sea change is underway. Up until now, and into 2006, the open source community has been playing catch-up with Microsoft, trying to make applications that do everything the typical Microsoft Windows user expects, in the same ubiquitous fashion. As I type this (September 2004) I can say that the open source community has almost caught up. OpenOffice.org is approaching version 2.0, and can already replace Microsoft Office for all but the most complex Word, Powerpoint, and Excel documents. Mozilla FireFox and ThunderBird are approaching 1.0, and already handle browsing and emailing better than Microsoft Internet Explorer and Outlook. The Gnome and KDE Linux desktops, besides being fast and stable, now have all the goodies any modern user would want. From now on, the catch-up phase is over, and real innovation will begin to be defined not by one huge company, but by a loosely-knit community of open source developers from all over the world. The big company will have to become a part of that community - and play by its rules - or risk being sucked into the undertow.

We aren't quite there yet. One very important missing piece for software development on Linux or Mac is a comprehensive Integrated Development Environment (IDE), such as Microsoft Visual Basic, that lets people develop powerful applications without necessarily having to know how to write great code. An IDE is basically a collection of power tools, such as a visual form designer, a graphical debugger, a project manager, and a help system. Until such an application becomes available, the only people developing open source applications will be people comfortable with the command line, make files, and a c compiler.

Enter Dabo, an open source, data aware, 3-tier development framework that you can use to develop open source and/or proprietary applications for distribution to your customers. Dabo aims to be easy to learn, fun to work with, flexible, and powerful. You can program by hand using any editor, or you can use the Dabo IDE which centralizes all the files in your project and offers all the power tools you need to create your databases, build your user interface, write your business rules, and create your reports (printouts or previews of your data, formatted the way you define).

Audience

The Dabo Book assumes some knowledge of a typical Microsoft-like IDE such as Visual Basic or Visual FoxPro, but that isn't required to get by. All that is really needed is the desire to create a killer application. Follow along with the examples, and you'll know all you need to know in no time at all.

If you happen to have experience with the Python programming language, which Dabo is built on top of, that is great but again not required.

In a nutshell, this book is for anyone wishing to develop a desktop application that will run on Linux, Macintosh, and Windows, and that will connect to an external database to get and store data.

How to Read this Book

Read this book with enough light, sitting up straight, and with your keyboard close at hand.

Conventions Used in This Book

Conventions are good, but let's not feel too limited by them, either.

Typographic Conventions

Constant width

Used for commands, command output, and switches

Constant width italic

Used for replaceable items in code and text

Italic

Used for file and directory names

Icons

Note

This icon designates a note relating to the surrounding text.

Tip

This icon designates a helpful tip relating to the surrounding text.

Warning

This icon designates a warning relating to the surrounding text.

The examples in this book are meant to illustrate the problem at hand in a concise manner. While they should work as written, they do not necessarily serve as examples of good programming style. They are probably pretty good though, given the conciseness and readability of the Python programming language.

Organization of This Book

The book is organized into several parts, and are abstracted here:

Part I, “Introduction”

Covers the history of Dabo as well as its features, architecture, components, and install methods. Also includes an introduction to the Python programming language and an explanation of the Dabo dual-license.

Part II, “Basic Concepts”

Explains the components that make up Dabo, shows some simple examples, introduces the new user to the Python language, and goes into some detail on the "Three Tiers of Dabo". Also shows the typical structure of a Dabo application, and the typical workflow patterns of creating a Dabo application.

Part III, “Tutorial: Creating a Non-Trivial Dabo Application”

Here is where the fun begins. Follow along with the design and implementation of a "real-world" data-aware application, from start to finish. From the structure of the database, to the writing of the business rules, to the designing of the screens and application framework, to debugging and documenting, to source control, this is the real meat of the book. This is the part you will want to read from beginning to end, once in bed and again in front of your computer. Get through this and you'll know how to start making your own application.

Part IV, “Class Reference”

You won't necessarily want to read this part of the book from beginning to end, but it'll probably be well thumbed through, as it'll be your reference to all the properties, methods, and events available for your use.

Part V, “Appendices”

A number of stand-alone articles are presented here, including options for reporting, debugging tactics, visual design options, how to contribute to the development of Dabo, as well as the full source of the Dabo Commercial License and the GNU Public License.

This Book is Free

This book is written in front of the public eye, and is constantly updated to include new information and fixes. It is free for you to do whatever you want with it, including modifying, printing, and reselling, although we'd prefer you'd purchase your printed copies from our official publisher and contribute your changes back to the book's source.

Note

The source of the book is publicly available from the DaboDoc Subversion repository, which you can access using your Subversion client:

svn checkout svn://paulmcnett.com/dabodoc/trunk/book dabo-book

Subversion is a powerful source control system, available for free from http://subversion.tigris.org.

See Appendix C, The Dabo Book Copyright for more information regarding the book's copyright.

Acknowledgments

I'd like to acknowledge the patience of my wife, Denise Dewenter, and the stimulating qualities of Yerba Mate. This book would not be possible without either one of these.

Introduction

The history of Dabo as well as its features, architecture, components, and install methods are covered. Also includes an introduction to the Python programming language, and an explanation of the Dabo dual-license.

Chapter 1. What Is Dabo?

Dabo provides an abstraction layer for a variety of open source projects, for the purpose of providing a solid and flexible framework for developing multiplatform data-aware business applications. User/developers can use the powerful Python programming language to write their business logic and lay out their user-interface elements, harnessing the Dabo framework and thus not getting preoccupied with the implementation details.

3-Tier

Dabo provides a 3-tier approach to application design, separating database access from business rules from user-interface layout. Dabo also provides an Application object that provides common functions and controls the event loop. This 3-tier relationship can be seen in Figure 1.1, “Dabo's 3-Tiers”.

Figure 1.1. Dabo's 3-Tiers

Dabo's 3-Tiers

Dabo allows you to use each tier independently, for instance only using the database tier for a simple script, or only using the UI tier for a simple GUI app that doesn't need database access. But those use-cases will be limited. In a typical Dabo application, 90% of the user code will end up in the business tier, using subclasses of the Dabo Business Object, 0% in the database tier, and the rest as layout code in the user-interface tier.

Dabo's tiers are related in a chain-of-responsibility pattern, so that when a user chooses, for example, to save their changes, the user-interface will communicate that request to the business object, which will validate the request against the business rules, and if all rules pass the request will go to the database tier to actually save the change to the database. If the change fails, such as in the business object for validation reasons, or in the database tier for connectivity reasons, the exception gets progagated back up to the user-interface tier and the user is notified.

Flexible Database Support

Dabo supports all databases for which there is a Python wrapper that conforms to the dbapi version 2. This includes all popular databases, as illustrated in Figure 1.2, “Supported databases.”.

Warning

Please note that this book is being written for future benefit, and that as of this writing the only supported databases are MySQL, Firebird, and Sqlite, and MySQL is the most tested.

Figure 1.2. Supported databases.

Supported databases.

Flexible User-Interface Support

After version 1.0, Dabo will support a selection of user-interface libraries, as illustrated in Figure 1.3, “Supported UI toolkits after 1.0”. The support of multiple libraries while allowing the Dabo developer to use a common API makes Dabo a very flexible, powerful solution. Different toolkits have their pros and cons; you can choose which one to deploy and not worry too much about that during development. You could conceivably do all your development using one toolkit and deploy with another. You may have one deployment using PyQt, and another using wxPython, both using the same codebase.

Warning

Please note that this book is being written for future benefit, and that as of this writing the only supported user interface is wxPython.

Figure 1.3. Supported UI toolkits after 1.0

Supported UI toolkits after 1.0

Flexible Platform Support

Dabo is truly multi-platform. Develop on any supported platform, and deploy the same code base to any supported platform. The supported platforms are Macintosh OS X (10.2 or higher), Linux, and Windows (98SE or higher). Figure 1.4, “Dabo application running on Linux”, Figure 1.5, “Dabo application running on Macintosh”, and Figure 1.6, “Dabo application running on Windows” show the same Dabo-developed application running on all three platforms.

Figure 1.4. Dabo application running on Linux

Dabo application running on Linux

Figure 1.5. Dabo application running on Macintosh

Dabo application running on Macintosh

Figure 1.6. Dabo application running on Windows

Dabo application running on Windows

Summary

Dabo is a framework built on Python that provides a clean API for developers to build data-aware business applications that are cross-platform. In addition to this underlying framework, Dabo also provides some power tools, such as a visual UI designer based on wxGlade, for designing and laying out your forms, menus, and other UI elements, and wizards and demo applications for getting started. These power tools are discussed elsewhere in this book.

Chapter 2. History of Dabo

Dabo is the result of a few years of research, starting in 2001 when I started taking an active interest in the Linux operating system and open source software in general. I had been using Microsoft Visual FoxPro to develop data-aware business applications for my clients, and with mixed messages coming from Microsoft and the FoxPro community as to the long-term viability of FoxPro as a product, I started looking for alternatives from the open source community, alternatives that would permit the development of powerful database applications for multi-platform deployment.

My quest led me first to Borland Delphi, which had just recently announced a prerelease version of Kylix, the Linux version of Delphi. This product would allow deployment of a semi-common codebase to one flavor of Linux (RedHat 7.x) and Windows. It had good data-aware controls, but only in the Enterprise version. The execution performance was pitiful, and code wasn't truly portable between platforms. The lack of a commitment from Borland to support Macintosh was the straw that made me look elsewhere.

The next stop was to take a serious look at Java, which does have a clean and elegant language and does run pretty much equally on all platforms, thanks to the Java Virtual Machine. I developed some prototype applications using the Swing components which performed equally horribly on all three platforms. But, it was easy development and deployment, although database integration was pretty obtuse compared to what I was used to in Visual FoxPro. My overall feeling was that Java may really take over the world, but it has a long way to go performance-wise, and it really isn't fun to code.

Somewhere about this time, I found out that Kylix was using a toolkit called Qt to provide the user interface components, and that Kylix was using the last-generation (Qt 2.x) instead of the newer, much nicer Qt 3.x version. I downloaded the GPL'd version of Qt for Linux, and followed some tutorials, and was able to build some very impressive C++ applications that performed very well. I never got my head around C++, however, so I felt like I'd be painting myself into a corner by pursuing this angle. Also, database support required the Enterprise version of Qt, which is something like $1200 per platform. Hardly free and open source.

Eventually, I came to know of a programming language called Python, which Ed Leafe had been using to power his website using a product called Zope. I found Python to be very intuitive to learn, with an easy readable syntax not unlike FoxPro. Python, on top of being free and open source, also comes with "batteries included", meaning that most everything you'd want to do comes in the standard library, including building user interfaces. Python also comes with three native data types that have to do with sequences - in other words, Python can represent database tables and fields natively. Come to find out, Python also provides an API for connecting to all kinds of database servers. In other words, Python comes with a lot of the pieces I'd been searching for over the past few years.

While it was great finding that Python had a lot of the pieces to my puzzle, it was another thing entirely realizing that putting all the pieces together into a workable whole would prove to be anything but easy. Yes, Python can connect to any database and retrieve and update data. Yes, Python has a graphical user interface. No, connecting to a given database isn't the same as connecting to a different database. And no, the user interface that comes with Python is not very modern.

In the Fall of 2003, I set out to create a framework for developing data-aware applications in Python, using a GUI toolkit I'd just learned about called wxWindows. I'd actually heard about wxWindows before, but had discounted it thinking that it was only for the Windows platform. They've since changed their name to wxWidgets at Microsoft's request, which may keep future developers from being as confused as I was. I named this framework 'Dabo', because it sounded fun and reminds me of words like 'data', 'business', 'application', and 'objects'. Also, we were watching Star Trek: Deep Space Nine at the time and I liked the Dabo Girls.

I ended up learning the wxWidgets toolkit pretty well, but created a pile of spaghetti code that had no separation between the database, the business rules, and the user interface. It was really a mess and completely unmaintainable. I set the project down for a few months, until I was contacted in March of 2004 by Ed Leafe, a long-time FoxPro guru that was looking for ways to move his skillset over to open source, multiplatform development - he was looking for the same things I'd been seeking over the previous couple years. I decided to share my code with him, along with a sample application, and he very diplomatically explained all the design problems with my approach. We came up with an agreement to redesign Dabo from the ground up, with a 3-tier model.

By May of 2004 we announced our work to the public, got a website and mailing lists, and encouragement from diverse areas of the open source, Python, and FoxPro communities. As I write this in September 2004, Dabo is under active development, the user interface is 80% abstracted, 3 databases are supported, and a user-interface graphical designer is underway. It is already possible to create powerful data-aware applications and to deploy them to Windows, Linux, and Macintosh.

Chapter 3. Procuring and Installing Dabo

This chapter details the process for getting your computer set up for developing applications using Dabo. While these instructions would work for your deployment targets as well, there are better ways to deploy applications than by using the instructions here. Dabo has dependencies on a number of external libraries, and while developing your applications you'll want to keep all those libraries - and Dabo - as current as possible. For deployment, you want better control over the versions in use. Deploying applications is covered in Appendix B, Application Deployment.

Downloading and Installing

Dabo has dependencies on a number of external libraries, and those dependencies will vary depending on your choice of database and user-interface library. In general, you will want to install, in this order:

  1. Python (the most recent stable version available). Python is available for download from http://www.python.org. Follow the instructions there for installation instructions[1] for your platform. Python is certainly already installed on your Linux or Macintosh system, but for Windows you may find that you need to install it yourself. No matter what, please check to see if the version you have is relatively stale and if so you should download and install the most recent stable Python release.

  2. MySQL Client.[2] MySQL is available for download from http://www.mysql.com. Download the most recent stable version available. If you already have a MySQL server installed somewhere on your network available to you (and you have the rights to create and drop databases), only the client is required. Otherwise, also install the server so that you'll be able to follow along with some later examples.

  3. MySQLdb. This is the Python db-api wrapper that allows your Python code to talk to the MySQL client library. This is an easy install using the standard Python distutils[3] method. Download MySQLdb from http://sourceforge.net/projects/mysql-python. Get the latest stable version.

  4. wxPython. This is the standard user-interface toolkit for Dabo, and at the time of this writing is required for building applications that present an interface to the user. In the future, Dabo will support other user-interface toolkits as well, but for now the only supported toolkit is wxPython. wxPython is in a state of rapid development, so it is best to stay as current as you can with it. Download and install the most recent stable version from http://www.wxpython.org.

  5. Dabo. Get the most recent version of Dabo from http://dabodev.com. Be sure to get the main Dabo package as well as DaboDemo. Like MySQLdb, Dabo uses distutils so a simple python setup.py install should get Dabo into your Python installation's site-packages directory, which is where all third-party libraries for Python are normally installed. DaboDemo doesn't install into site-packages, however, so just put it somewhere where you can find it later, such as in your home directory.

Testing Your Install

Now that you've downloaded and installed all the prerequisites, you need to run some tests to be reasonably sure everything is installed correctly. The tests involve interacting with your operating system's command line, which as a developer you really should try to get familiar with.

  • Microsoft Windows: Go to Start|Run and type cmd and press Enter.

  • Apple Macintosh: Navigate to your Applications/Utilities directory and double-click on Terminal.

  • Linux and Unix: Different distributions put this in different places. Look for Terminal or xterm or Command Line in your desktop menu system.

Open up your command line, and type python. You should get output like:

[pmcnett@sol book]$ python
Python 2.3.2 (#1, Oct  6 2003, 10:07:16)
[GCC 3.2.2 20030222 (Red Hat Linux 3.2.2-5)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>

You are now inside Python's command interpreter. Test to make sure that MySQLdb, wxPython, and dabo load correctly. If there are no errors, they are installed correctly.

>>> import MySQLdb
>>> import wx
>>> import dabo
Dabo Info Log: Thu Sep  9 19:16:23 2004: No default UI set. (DABO_DEFAULT_UI)
>>>

The message from dabo is normal, and no errors happened during the import of the other packages, so everything is set up correctly on my system.



[1] Because installation instructions for external dependencies are subject to change and out of the control of Dabo, it is best to refer the reader directly to the authority for that external dependency, instead of trying to provide exhaustive instructions here.

[2] MySQL is not required for Dabo, but all the examples in this book and most of the demos included with Dabo use MySQL. So while your ultimate project may not use MySQL, please install it anyway so that you can follow along and learn how to use Dabo.

[3] Without concerning yourself with the details, most Python third-party packages use distutils. What this means for you is that all you need to do to install the library is to enter the command python setup.py install from within the directory in which you expanded the downloaded library.

Chapter 4. Introductory Python

Python is an open source interpreted language that runs on all kinds of operating systems from embedded systems to super computers. It has a natural, almost pseudo-code syntax, and is easy to read and write. Python's standard library comes with almost everything you'd want to do, from processing email to working with multi-threading to regular expressions. Python is evolving steadily, introducing new features at a reasonable rate. As I write this, Python 2.4 is around the corner and will include support for the decimal type, which is an important data type for business applications.[4]

Because of Python's approachablility, flexibility, and power, we chose to program Dabo in Python. What this means to you is that all your Dabo code will be Python code, so this chapter will introduce you to some of Python's basic concepts. It doesn't try to be a full Python tutorial or reference, as there are already plenty of those[5], but this chapter should present enough of Python to get you going.

Note

This chapter provides an introduction to Python from the point of view of developing using Dabo. It skips over some important stuff in the interest of being brief and staying on track. Please do go through a tutorial or two at http://www.python.org in addition to reading through this chapter. There is no shortcut for trying things out hands-on!

If you are already experienced with Python, you can skip this chapter. Otherwise, get ready to have some fun!

Indentation

Unlike most other languages, whitespace is significant in Python. Python does not force the use of ending delimiters for blocks, but rather determines the block structure based on relative indentation. Whether you use tabs or spaces doesn't matter, as long as you are consistent[6].

Block indentation means that Python code is shorter and cleaner, and also that it executes like you'd expect because the block structure as it runs is as it appears in the code. If the lack of an ending delimeter really bothers you, you can always use comments in a clever way, such as:

	
if x > y:
	print "x is greater"
#endif

or even:

	
if x > y: #{
	print "x is greater"
#}

The Python Interactive Interpreter

A very handy feature of Python, and one that I always appreciated in FoxPro, is the interactive interpreter. Here, you can type in arbitrary Python code and it will be executed, line-by-line. This is the best place to try things out, and to experiment, to see what Python's behavior is.

To fire up the interpreter, just type python from your command prompt[7], and you'll then be in the interpreter with the Python prompt of >>>:

	
[pmcnett@sol book]$ python
Python 2.3.2 (#1, Oct  6 2003, 10:07:16)
[GCC 3.2.2 20030222 (Red Hat Linux 3.2.2-5)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>

Follow along with some examples, to see how easily Python can be picked up by a beginner:

Example 4.1. Getting Started with the Python Interpreter

	
>>> x = 23
>>> y = 42
>>> print x
23
>>> print y
42
>>> s = "Today I had %s eggs. Yesterday I had %s." % (x, y)
>>> print s
Today I had 23 eggs. Yesterday I had 42.
>>> x += x
>>> print x
46
>>> print s
Today I had 23 eggs. Yesterday I had 42.
>>> myList = ["eggs", "ham", "spam"]
>>> print myList
['eggs', 'ham', 'spam']
>>> myList.append("bacon")
>>> print myList
['eggs', 'ham', 'spam', 'bacon']
>>> print myList[0]
eggs
>>> print myList[len(myList) - 1]
bacon
>>> print myList[len(myList)]
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
IndexError: list index out of range
>>> print myList[0:1]
['eggs']
>>> print myList[0:3]
['eggs', 'ham', 'spam']

In Example 4.1, “Getting Started with the Python Interpreter” above, the integer values 23 and 42 are bound to the names x and y, respectively. In Python, it is best to think in terms of values being bound to names, rather than thinking in terms of variables. This is because of Python's use of namespaces, containers of names. Once you start thinking in these terms, you won't be scratching your head over issues such as whether a given variable is global or private in scope. With namespaces, each name exists in it's own namespace and nowhere else [8] .

Then, a string value is bound to name s. Additionally, Python's string substitution is shown.

A list is defined, with four elements. The list is one of three sequence data types that Python has to offer. The list and the tuple handle indexed lists, the difference being that lists are mutable and tuples are not. The list can be sliced, or referenced by index, as shown in the lines following the list definition. Python uses zero-based indexing, so referencing index 4 results in an IndexError.

Exception Handling

Exceptions, or errors, happen for a variety reasons. It could be that there is a syntax error in your code, or it could be that something out of programmatic control has taken place, such as a disk running out of free space. When an exception occurs, Python creates an Exception object and lets your code handle it, or to choose not to handle it. Your code can handle exceptions by using the try...except block structure. Whenever you write code that assumes too much about the current runtime environment, it is good to encapsulate that code in a try...except block so that you can react appropriately when your assumptions don't pan out. See Example 4.2, “The try...except block” for some inspiration.

Example 4.2. The try...except block

	
# x may or may not be defined yet...
try:
	y = x/2
except NameError:
	y = None
print y

If x had been defined already, y would get the value of x divided by 2. Otherwise, y would get the value of None. None is roughly analagous to NULL in other languages - it is a data type all its own, and represents a value of "no value".

If an exception isn't handled in your code, a traceback message will be displayed on your console explaining the error, along with the module, line number, and calling stack trace. This information will let you quickly debug your code during the testing phase.

In general, it is considered Pythonic to let exception handling take care of weeding out argument checking problems, instead of manually checking for correct types first. Example 4.3, “Using try...except to handle type checking”, offers two ways to handle type checking, but the latter is considered the correct way to do it in Python.

Example 4.3. Using try...except to handle type checking

		
def nonPythonic(stringArg):
	"""Given a string, add some spam to it."""
	if type(stringArg) != type(str()):
		raise TypeError, "Must send a string"
		return
	stringArg += " is spam"
	return stringArg
	
def pythonic(stringArg):
	"""Given a string, add some spam to it."""
	return stringArg + " is spam"

Type checking serves to lengthen code uselessly. Both the nonPythonic and pythonic versions will raise the appropriate exception, but the pythonic version lets Python handle it and is much shorter and simpler.

You may define your own custom exceptions by subclassing Exception, and you may raise any exception with the raise command. Try...except may be nested, and there are additional features not shown here, such as the finally clause and the ability to explicitly handle any number of exceptions.

Namespaces and Modules

The Python standard library and third-party packages are used by importing them into their own namespaces. For instance, the statement import sys brings in a number of system-related functions and names into the sys namespace. Use the built-in dir() function to see all the names in a namespace.

Example 4.4. Importing a module

	
[pmcnett@sol book]$ python
Python 2.3.2 (#1, Oct  6 2003, 10:07:16)
[GCC 3.2.2 20030222 (Red Hat Linux 3.2.2-5)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', 
'__name__', '__stderr__', '__stdin__', 
'__stdout__', '_getframe', 'api_version', 'argv', 
'builtin_module_names', 'byteorder', 
'call_tracing', 'callstats', 'copyright',
'displayhook', 'exc_clear', 'exc_info', 
'exc_type', 'excepthook', 'exec_prefix', 
'executable', 'exit', 'getcheckinterval', 
'getdefaultencoding', 'getdlopenflags', 
'getfilesystemencoding', 'getrecursionlimit', 
'getrefcount', 'hexversion', 'maxint', 
'maxunicode', 'meta_path', 'modules', 'path', 
'path_hooks', 'path_importer_cache', 'platform', 
'prefix', 'ps1', 'ps2', 'setcheckinterval', 
'setdlopenflags', 'setprofile', 
'setrecursionlimit', 'settrace', 'stderr', 
'stdin', 'stdout', 'version', 'version_info', 
'warnoptions']
>>> print sys.version
2.3.2 (#1, Oct  6 2003, 10:07:16)
[GCC 3.2.2 20030222 (Red Hat Linux 3.2.2-5)]
>>> print sys.path
['', '/home/pmcnett/wxPythonSrc/wxPython', 
'/usr/lib/python23.zip', '/usr/lib/python2.3', 
'/usr/lib/python2.3/plat-linux2', 
'/usr/lib/python2.3/lib-tk', 
'/usr/lib/python2.3/lib-dynload', 
'/usr/lib/python2.3/site-packages', 
'/usr/lib/python2.3/site-packages/PythonCAD', 
'/usr/local/gnue/lib/python']
>>> print sys.exit()
[pmcnett@sol book]$

Importing external modules is how to get things done in Python: most likely, the function you want has already been written. You just need to find the module, install it if necessary, import it, and call the needed function or instantiate the needed class.

Data Types

Sequence Types

As mentioned before, Python comes with three data types that can represent data records and tables:

  • A list is a mutable sequence of arbitrary elements. Elements are referenced by index. The list is denoted with square brackets, and can be created several ways:

    			
    >>> myList = []  # empty list
    >>> myList = [1, "apple"]  # new list with 2 elements
    >>> myList = list()  # new empty list
    >>> myList.append("orange")  # add an element
    >>> print myList
    ['orange']
    
    
  • A tuple is an immutable sequence of arbitrary elements. Like the list, tuple elements are referenced by index. The tuple is denoted with parentheses, and can be created several ways:

    			
    >>> myTuple = ()  # empty tuple
    >>> myTuple = (1, "apple")  # new tuple with 2 elements
    >>> myTuple = tuple()  # new empty tuple
    
    

    Because tuples are immutable, there is no append() method.

  • A dictionary is a mutable grouping of arbitrary elements, in arbitrary order, referenced by a key instead of an index. The dictionary is denoted with curly brackets, and can be created several ways:

    			
    >>> myDict = {}  # new empty dictionary
    >>> myDict = {"id": 1, "fruit": "apple"}  # new dictionary with two key/value pairs
    >>> myDict = dict()
    >>> myDict["id"] = 2
    >>> myDict["fruit"] = "pear"
    >>> print myDict
    {'fruit': 'pear', 'id': 2}
    
    

The only difference between list and tuple is that tuples are immutable, meaning you cannot add an element or delete an element - you would instead need to create a new tuple. Tuples are more efficient, and safer to pass around as parameters because of Python's "pass by reference" mode of parameter passing. If you pass a mutable object as a parameter to another function, and that function changes something in that object, that change will reflect in the caller as well. Since tuples are immutable, any change to the tuple in the called function will result in a new tuple at a new memory address, preserving the version in the calling function.

You can easily convert from list to tuple and back again:

	
>>> myList = ["apple", "pear", "orange"]
>>> print myList
['apple', 'pear', 'orange']
>>> myTuple = tuple(myList)
>>> print myTuple
('apple', 'pear', 'orange')
>>> myList = list(myTuple)

Lists and tuples can be sliced, which is simply a notation for accessing a given element or group of elements - the result of the slice is a list of the matching elements.

	
>>> myList = ["orange", "apple", "grape"]
>>> slice = myList[:2]
>>> print slice
['orange', 'apple']
>>> print myList[1:]
['apple', 'grape']
>>> print myList[:]
['orange', 'apple', 'grape']

Lists and tuples can also be easily iterated over using the for construct.

	
>>> fruits = ["apple", "banana", "mango"]
>>> for fruit in fruits:
...     print fruit
...
apple
banana
mango

Dictionaries are very useful for storing name/value pairs. Think about a field name and a field value, for example.

The real power of the list, tuple, and dictionary come not by using them standalone, but by embedding them inside each other. For example, a database customer table could be expressed in Python using a list of records, where each record is a dictionary of field name/value pairs.Example 4.5, “A list of dictionaries representing a data table” shows an example of just such a construct, albeit manually created instead of pulled from a real database.

Example 4.5. A list of dictionaries representing a data table

		
>>> table = []
>>> record = {"cCompanyName": "YYY Chiropractic", "nMaxCredit": 50000}
>>> table.append(record)
>>> record = {"cCompanyName": "DMP Productions", "nMaxCredit": 10}
>>> table.append(record)
>>> print table
[{'nMaxCredit': 50000, 'cCompanyName': 'YYY Chiropractic'}, {'nMaxCredit': 10, 'cCompanyName': 'DMP Productions'}]
>>> for record in table:
...     print record["cCompanyName"], ": ", record["nMaxCredit"]
...
YYY Chiropractic :  50000
DMP Productions :  10
>>>		

Other Frequently-Used Types

In addition, there are a few other data types worth noting for business database use.

  • The str type stores string values, and is an immutable sequence of bytes. In other words, it is a tuple and can be sliced.

  • The int type stores immutable signed integer values.

  • The float type is architecture-dependent and while it can represent very small numbers there will be slight rounding errors with even the simplest division. The decimal type should be used instead for most business application uses.

  • The decimal type is good for representing currency and any real numbers where scientific precision isn't needed. The decimal section will get truncated to an arbitrary number of places.

  • The bool type is immutable and can be one of True or False.

  • The datetime.date and datetime.datetime types can be used to represent dates and times. You must first import datetime.

  • The None type conveys "no value" or "null".

Functions and Classes

Functions and classes are really separate topics that should be discussed independently, but for the purpose of this simple introduction I'll talk about them together. They actually do have some things in common, not least of which being that they are both first-class objects [9] .

Functions and classes are really easy to define. A function defined inside a class is also known as a method. In Example 4.6, “Function and Class Definitions” below, a function and a class are defined.

Example 4.6. Function and Class Definitions

	
def myFunction(name="John Doe"):
        return "Hello, %s." % name

class MyClass(object):
        def __init__(self, name="Mary Jane"):
                # save the name parameter to the Name property:
                self.Name = name

        def greet(self):
                print myFunction(self.Name)

        def _getName(self):
                return self._name

        def _setName(self, val):
                self._name = val

        Name = property(_getName, _setName)

# Print the function object:
print myFunction

# Print the class object:
print MyClass

# Print the function's return value:
print myFunction()

# Instantiate the class and do something with it:
c = MyClass("Paul")
c.greet()


Run the code, and you should see the following output:

	
[pmcnett@sol pmcnett]$ python test.py
<function myFunction at 0x40176844>
<class '__main__.MyClass'>
Hello, John Doe.
Hello, Paul.

The arguments, or parameters, to functions and methods can be ordered or named. Methods of classes automatically receive a first argument of 'self', which is simply a reference to the class instance. The __init__() method is where parameters sent to the class constructor are received. Properties provide "getters and setters" to manipulate the data of the class instance.

Arguments are passed by reference, not by value. This is actually something that won't matter in the majority of cases, though, because of the immutability of the most common data types. A string, for instance, is an immutable sequence of bytes. If I were to pass a string to a function, and then change the string in the function, what actually happens is that the string gets copied to a new memory address, and then the name gets rebound, so the original string value in the caller is left intact. The only time pass-by-reference can bite you is when you send lists or dictionaries as arguments. Because lists and dicts are mutable, changes to these from within the function will change the them in the caller as well, because they both refer to the same memory address.

Tip

A great way to pass a list by value is to convert it to a tuple first, like so:

myList = [1,2,3]
myFunction(tuple(myList))

This will result in myFunction receiving a tuple, which it can convert to a list for its own use if necessary, and it will not touch the original myList in the caller.

Connecting To Databases

As you can see if you've followed along so far with this chapter, Python contains some very cool features for database representation. The list and dict types can express rows and columns in a dataset, and the base data types such as str and int can express the field values. So the next question is how to get data out of a database, and how to save data back to the database. Helpfully, Python has the answer to that too.

Python has a database API, known affectionately as "The Python Database API Specification Version 2.0" [10] . It specifies a common interface for implementors to conform to when writing Python wrappers to client database drivers. All popular databases have Python wrappers written, which means all that is necessary to connect to a given database server is to install the client library as well as the Python wrapper.

If you followed the instructions for installing MySQL in Chapter 3, Procuring and Installing Dabo, you can connect to a MySQL server using the following procedure. This should work as written, as this test database on my server is exposed to the world.

Example 4.7. Connecting to a MySQL database


>>> import MySQLdb
>>> connection = MySQLdb.connect(host="melder.paulmcnett.com", 
user="dabo", passwd="dabo", db="dabotest")
>>> cursor = connection.cursor()
>>> cursor.execute("show tables")
3L
>>> recordSet = cursor.fetchall()
>>> print recordSet
(('reccat',), ('reccats',), ('recipes',))
>>> cursor.execute("select ctitle from recipes where ctitle like '%manicotti%'")
2L
>>> recordSet = cursor.fetchall()
>>> for record in recordSet:
...     print record
...
('Easy Manicotti',)
('Manicotti with Red Bell Pepper Sauce',)
		

Note

You don't really need to care about the specifics of connecting to and querying/updating databases, as Dabo shields much of that from you. It does help to have some level of basic understanding of what is going on, though.


[4] Prior to Python 2.4, the float data type would be used to express decimal values, with the problem that float is architecture-dependent and would introduce subtle rounding errors. See http://docs.python.org/tut/node15.html for a discussion of this.

[5] The best place to start learning Python is the official Python website, at http://www.python.org, which contains all of Python's official documentation, as well as several well-written tutorials.

[6] The Dabo code indents with the Tab key exclusively, but most Python code out there still indents with Space. We believe that Tab is the best key to indent with, and that eventually Tab will win out in this religious war. You are free to make your own choice which to use, but you need to be consistent.

[7] See the section called “Testing Your Install” for instructions on finding your command prompt.

[8] However, it is possible to bring in names from other namespaces, for example by issuing from module import *. While possibly convenient, this practice is heartily discouraged.

[9] A first-class object is an object that can be passed around as a parameter and a return value. In Python, this includes not only class instances, but also class definitions, function definitions, and types. You could define a function, bind it to a name (def myFunc(c) binds the function object to the name myFunc, and send that function object somewhere else for evaluation.

[10] You can read all about it in PEP (Python Enhancement Proposal) 249 at http://www.python.org/peps/pep-0249.html.

Chapter 5. ??? Placeholder ???

Basic Concepts

Chapter 6. Dabo Objects

dObject of My Affection

Every public class in Dabo has a lowercase 'd' prepended, and dObject is the most basic of all the Dabo classes. Every class in the Dabo framework descends from dObject, which provides general functionality, basic properties, and event handling. You will rarely, if ever, create classes that subclass dObject directly, but understanding everything that dObject provides will greatly help your understanding of Dabo.

Note

See Part IV, “Class Reference”, which covers all the properties, events, and methods of dObject and every other object in detail. The section here only covers those properties, events, and methods that you will commonly use in practice.

Figure 6.1. All objects are based on dObject.

All objects are based on dObject.

dObject Properties

Properties in Dabo always start with an upper-case letter, and are in inter-caps style, such as LogEvents and Name. Properties are best thought of as class instance variables: they provide a place to store an important value. dObject provides the most basic properties used in Dabo, and are listed here.

  • Application stores a reference to the Application object in use, which will be a subclass of dApp, or None if there is no Application object in use. This is basically a convenience, and means that accessing features of dApp is as easy as writing something like sm = self.Application.SecurityManager.

  • LogEvents is a list of events to log, and is useful for debugging purposes. If you want to track all events, set it to ["All"]. If you want to track just certain events, set it explicitly, as in ["GotFocus", "LostFocus"]. And finally, if you want to see all events except for some other events, set it to something like ["All", "Idle", "Paint", "MouseMove"].

  • Name stores the object's identifier, which must be unique among siblings. If Application.AutoNegotiateUniqueNames is set to True (the default), a non-unique Name assignment will result in an integer getting appended to the name to make it unique. Otherwise, a Python ValueError exception will be raised.

  • Parent stores an object reference to the containing object, or None if there is no parent.

dObject Methods

dObject provides a few methods that you will call over and over again in your code.

  • bindEvent() lets your custom code run whenever a certain event occurs. For instance, if the user presses a CommandButton, you will usually want your form to take some action. To accomplish this, you need to define a method of your form to act as the callback function, and call the command button's bindEvent() method to link the event with the callback function. For example, see Example 6.1, “Subclass of dCheckBox” where the custom onHit() method is run whenever the user changes the state of the CheckBox. The full syntax of bindEvent() is:

    self.bindEvent(EventClass, CallbackFunction)
    

    See also the EventBindings property, the unBindEvent() method, and the complete list of Dabo events, which are detailed in Part IV, “Class Reference”.

  • doDefault() simply runs the code in the superclass method. The full syntax is:

    ret = class.doDefault([arg1[, arg2...]])
    

    This is slightly easier than using Python's super() function. The class is the class reference that self is an instance of. For example, here is a full class definition, showing the use of doDefault():

    Example 6.1. Subclass of dCheckBox

    import dabo
    
    dabo.ui.loadUI("wx")
    
    class MyCheckBox(dabo.ui.dCheckBox):
    	def afterInit(self):
    		MyCheckBox.doDefault()
    		self.Caption = "Test Checkbox"
    		self.bindEvent(dabo.dEvents.Hit, self.onHit)
    		
    	def onHit(self, evt):
    		print "Checkbox hit!"
    
    if __name__ == "__main__":
    	app = dabo.dApp()
    	app.setup()
    	app.MainFrame.addObject(MyCheckBox, "chkTest")
    	app.start()
    
    

    Note

    The above code is a working script, and will show your checkbox and will print your message when clicked, if you save it to disk and run it with
    python test.py
    

    Line 7 in the example code calls doDefault(), which runs the code in the superclass of MyCheckBox. In this case, it runs the code in dabo.ui.dCheckBox.afterInit() and sends no parameters. You can keep the superclass code from running by not calling doDefault().

    In general, always call doDefault(), unless you know that you definitely want to suppress the superclass code.

  • raiseEvent() lets you manually raise a Dabo event, or your own custom event based on dEvent. The full syntax is:

    self.raiseEvent(EventClass)
    

Tutorial: Creating a Non-Trivial Dabo Application

Class Reference

Appendices

Appendix A. Reporting Options

Table of Contents

ReportLab

This appendix is a guide for the current reporting options with Dabo, and concentrates on a product called ReportLab.

ReportLab

Blah

Appendix B. Application Deployment

This appendix details the procedure for deploying your application on all target platforms. You need to be in control of the versions of all external libaries, Dabo, and your application, which basically means some sort of runtime library for Dabo is necessary. The story is a bit different for developing Dabo applications - for that you probably want to install all of Dabo's dependencies separately so that you can keep them updated as needed. See Chapter 3, Procuring and Installing Dabo for instructions on setting up your development environment.

Appendix C. The Dabo Book Copyright


Copyright (c) 2004-2005 Paul McNett

This work is licensed under the Creative Commons Attribution License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by/2.0/ or send a letter to
Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305,
USA.

A summary of the license is given below, followed by the full legal
text.

--------------------------------------------------------------------

You are free:

	* to copy, distribute, display, and perform the work
	* to make derivative works
	* to make commercial use of the work

Under the following conditions:

Attribution. You must give the original author credit.

	* For any reuse or distribution, you must make clear to others the
	license terms of this work.

	* Any of these conditions can be waived if you get permission from
	the author.

Your fair use and other rights are in no way affected by the above.

The above is a summary of the full license below.

====================================================================

Creative Commons Legal Code
Attribution 2.0

CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
DAMAGES RESULTING FROM ITS USE.

License

THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS
CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS
PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE
WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS
PROHIBITED.

BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND
AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS
YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF
SUCH TERMS AND CONDITIONS.

1. Definitions

a. "Collective Work" means a work, such as a periodical issue,
	anthology or encyclopedia, in which the Work in its entirety in
	unmodified form, along with a number of other contributions,
	constituting separate and independent works in themselves, are
	assembled into a collective whole. A work that constitutes a
	Collective Work will not be considered a Derivative Work (as
	defined below) for the purposes of this License.

b. "Derivative Work" means a work based upon the Work or upon the
	Work and other pre-existing works, such as a translation,
	musical arrangement, dramatization, fictionalization, motion
	picture version, sound recording, art reproduction, abridgment,
	condensation, or any other form in which the Work may be recast,
	transformed, or adapted, except that a work that constitutes a
	Collective Work will not be considered a Derivative Work for the
	purpose of this License. For the avoidance of doubt, where the
	Work is a musical composition or sound recording, the
	synchronization of the Work in timed-relation with a moving
	image ("synching") will be considered a Derivative Work for the
	purpose of this License.

c. "Licensor" means the individual or entity that offers the Work
	under the terms of this License.

d. "Original Author" means the individual or entity who created the Work.

e. "Work" means the copyrightable work of authorship offered under
	the terms of this License.

f. "You" means an individual or entity exercising rights under this
	License who has not previously violated the terms of this
	License with respect to the Work, or who has received express
	permission from the Licensor to exercise rights under this
	License despite a previous violation.

2. Fair Use Rights. Nothing in this license is intended to reduce,
limit, or restrict any rights arising from fair use, first sale or
other limitations on the exclusive rights of the copyright owner
under copyright law or other applicable laws.

3. License Grant. Subject to the terms and conditions of this License,
Licensor hereby grants You a worldwide, royalty-free,
non-exclusive, perpetual (for the duration of the applicable
copyright) license to exercise the rights in the Work as stated
below:

a. to reproduce the Work, to incorporate the Work into one or more
	Collective Works, and to reproduce the Work as incorporated in
	the Collective Works;

b. to create and reproduce Derivative Works;

c. to distribute copies or phonorecords of, display publicly,
	perform publicly, and perform publicly by means of a digital
	audio transmission the Work including as incorporated in
	Collective Works;

d. to distribute copies or phonorecords of, display publicly,
	perform publicly, and perform publicly by means of a digital
	audio transmission Derivative Works.

e.

	For the avoidance of doubt, where the work is a musical composition:

		i. Performance Royalties Under Blanket Licenses. Licensor
			waives the exclusive right to collect, whether
			individually or via a performance rights society
			(e.g. ASCAP, BMI, SESAC), royalties for the public
			performance or public digital performance (e.g. webcast)
			of the Work.

		ii. Mechanical Rights and Statutory Royalties. Licensor waives
			the exclusive right to collect, whether individually or
			via a music rights agency or designated agent (e.g. Harry
			Fox Agency), royalties for any phonorecord You create from
			the Work ("cover version") and distribute, subject to the
			compulsory license created by 17 USC Section 115 of the US
			Copyright Act (or the equivalent in other jurisdictions).

f. Webcasting Rights and Statutory Royalties. For the avoidance of
	doubt, where the Work is a sound recording, Licensor waives the
	exclusive right to collect, whether individually or via a
	performance-rights society (e.g. SoundExchange), royalties for
	the public digital performance (e.g. webcast) of the Work,
	subject to the compulsory license created by 17 USC Section 114
	of the US Copyright Act (or the equivalent in other
	jurisdictions).

The above rights may be exercised in all media and formats whether now
known or hereafter devised. The above rights include the right to make
such modifications as are technically necessary to exercise the rights
in other media and formats. All rights not expressly granted by
Licensor are hereby reserved.

4. Restrictions.The license granted in Section 3 above is expressly
made subject to and limited by the following restrictions:

a. You may distribute, publicly display, publicly perform, or
	publicly digitally perform the Work only under the terms of this
	License, and You must include a copy of, or the Uniform Resource
	Identifier for, this License with every copy or phonorecord of
	the Work You distribute, publicly display, publicly perform, or
	publicly digitally perform. You may not offer or impose any
	terms on the Work that alter or restrict the terms of this
	License or the recipients' exercise of the rights granted
	hereunder. You may not sublicense the Work. You must keep intact
	all notices that refer to this License and to the disclaimer of
	warranties. You may not distribute, publicly display, publicly
	perform, or publicly digitally perform the Work with any
	technological measures that control access or use of the Work in
	a manner inconsistent with the terms of this License
	Agreement. The above applies to the Work as incorporated in a
	Collective Work, but this does not require the Collective Work
	apart from the Work itself to be made subject to the terms of
	this License. If You create a Collective Work, upon notice from
	any Licensor You must, to the extent practicable, remove from
	the Collective Work any reference to such Licensor or the
	Original Author, as requested. If You create a Derivative Work,
	upon notice from any Licensor You must, to the extent
	practicable, remove from the Derivative Work any reference to
	such Licensor or the Original Author, as requested.

b. If you distribute, publicly display, publicly perform, or
	publicly digitally perform the Work or any Derivative Works or
	Collective Works, You must keep intact all copyright notices for
	the Work and give the Original Author credit reasonable to the
	medium or means You are utilizing by conveying the name (or
	pseudonym if applicable) of the Original Author if supplied; the
	title of the Work if supplied; to the extent reasonably
	practicable, the Uniform Resource Identifier, if any, that
	Licensor specifies to be associated with the Work, unless such
	URI does not refer to the copyright notice or licensing
	information for the Work; and in the case of a Derivative Work,
	a credit identifying the use of the Work in the Derivative Work
	(e.g., "French translation of the Work by Original Author," or
	"Screenplay based on original Work by Original Author"). Such
	credit may be implemented in any reasonable manner; provided,
	however, that in the case of a Derivative Work or Collective
	Work, at a minimum such credit will appear where any other
	comparable authorship credit appears and in a manner at least as
	prominent as such other comparable authorship credit.

5. Representations, Warranties and Disclaimer

UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING,
LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR
WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED,
STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF
TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE,
NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY,
OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT
DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED
WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.

6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY
APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY
LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE
OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE
WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.

7. Termination

a. This License and the rights granted hereunder will terminate
	automatically upon any breach by You of the terms of this
	License. Individuals or entities who have received Derivative
	Works or Collective Works from You under this License, however,
	will not have their licenses terminated provided such
	individuals or entities remain in full compliance with those
	licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any
	termination of this License.

b. Subject to the above terms and conditions, the license granted
	here is perpetual (for the duration of the applicable copyright
	in the Work). Notwithstanding the above, Licensor reserves the
	right to release the Work under different license terms or to
	stop distributing the Work at any time; provided, however that
	any such election will not serve to withdraw this License (or
	any other license that has been, or is required to be, granted
	under the terms of this License), and this License will continue
	in full force and effect unless terminated as stated above.

8. Miscellaneous

a. Each time You distribute or publicly digitally perform the Work
	or a Collective Work, the Licensor offers to the recipient a
	license to the Work on the same terms and conditions as the
	license granted to You under this License.

b. Each time You distribute or publicly digitally perform a
	Derivative Work, Licensor offers to the recipient a license to
	the original Work on the same terms and conditions as the
	license granted to You under this License.

c. If any provision of this License is invalid or unenforceable
	under applicable law, it shall not affect the validity or
	enforceability of the remainder of the terms of this License,
	and without further action by the parties to this agreement,
	such provision shall be reformed to the minimum extent necessary
	to make such provision valid and enforceable.

d. No term or provision of this License shall be deemed waived and
	no breach consented to unless such waiver or consent shall be in
	writing and signed by the party to be charged with such waiver
	or consent.

e. This License constitutes the entire agreement between the
	parties with respect to the Work licensed here. There are no
	understandings, agreements or representations with respect to
	the Work not specified here. Licensor shall not be bound by any
	additional provisions that may appear in any communication from
	You. This License may not be modified without the mutual written
	agreement of the Licensor and You.

Creative Commons is not a party to this License, and makes no warranty
whatsoever in connection with the Work. Creative Commons will not be
liable to You or any party on any legal theory for any damages
whatsoever, including without limitation any general, special,
incidental or consequential damages arising in connection to this
license. Notwithstanding the foregoing two (2) sentences, if Creative
Commons has expressly identified itself as the Licensor hereunder, it
shall have all rights and obligations of Licensor.

Except for the limited purpose of indicating to the public that the
Work is licensed under the CCPL, neither party will use the trademark
"Creative Commons" or any related trademark or logo of Creative
Commons without the prior written consent of Creative Commons. Any
permitted use will be in compliance with Creative Commons'
then-current trademark usage guidelines, as may be published on its
website or otherwise made available upon request from time to time.

Creative Commons may be contacted at http://creativecommons.org/.

====================================================================

Index

Symbols

3-tier, 3-Tier