[Zodb-checkins] CVS: StandaloneZODB/Doc/guide - Makefile:1.1.2.1 README:1.1.2.1 TODO:1.1.2.1 admin.tex:1.1.2.1 chatter.py:1.1.2.1 gfdl.tex:1.1.2.1 indexing.tex:1.1.2.1 introduction.tex:1.1.2.1 links.tex:1.1.2.1 modules.tex:1.1.2.1 prog-zodb.tex:1.1.2.1 storages.tex:1.1.2.1 transactions.tex:1.1.2.1 zeo.tex:1.1.2.1 zodb.dvi:1.1.2.1 zodb.tex:1.1.2.1

Barry Warsaw barry@wooz.org
Mon, 17 Dec 2001 14:53:14 -0500


Update of /cvs-repository/StandaloneZODB/Doc/guide
In directory cvs.zope.org:/tmp/cvs-serv27733

Added Files:
      Tag: StandaloneZODB-1_0-branch
	Makefile README TODO admin.tex chatter.py gfdl.tex 
	indexing.tex introduction.tex links.tex modules.tex 
	prog-zodb.tex storages.tex transactions.tex zeo.tex zodb.dvi 
	zodb.tex 
Log Message:
Copy by value Andrew's zodb.sf.net programmers guide into our SAZ 1.0 branch.  Andrew's should still be considered the canonical version for now.



=== Added File StandaloneZODB/Doc/guide/Makefile ===

PYTHONSRC=$$HOME/projects/python

TEX_FILES = gfdl.tex introduction.tex  modules.tex  prog-zodb.tex  \
            storages.tex  transactions.tex  zeo.tex  zodb.tex 

MKHOWTO=$(PYTHONSRC)/Doc/tools/mkhowto

zodb.dvi: $(TEX_FILES)
	$(MKHOWTO) --dvi zodb.tex

html:
	-rm -rf zodb
	$(MKHOWTO) --html --iconserver=/python/writing/icons zodb.tex
#	rm -f /home/amk/www/zodb/guide/*
#	cp -p zodb/* /home/amk/www/zodb/guide/





=== Added File StandaloneZODB/Doc/guide/README ===
This directory contains Andrew Kuchling's programmers guide to ZODB
and ZEO.  It is taken from Andrew's zodb.sf.net project on
SourceForge.  The SF version should be considered the canonical
version, so don't make edits here for now.

Eventually, we'll finish merging the two projects and then the version
here will be the canonical one.  For now, we have to manually keep the
two directories in sync.


=== Added File StandaloneZODB/Doc/guide/TODO ===
Update text to use BTrees, not BTree
Write section on __setstate__
Connection.sync seems to work now; note this
Continue working on it
Suppress the full GFDL text in the PDF/PS versions



=== Added File StandaloneZODB/Doc/guide/admin.tex ===

%  Administration
%    Importing and exporting data
%    Disaster recovery/avoidance
%    Security



=== Added File StandaloneZODB/Doc/guide/chatter.py ===

import sys, time, os, random

from ZEO import ClientStorage
import ZODB
from ZODB.POSException import ConflictError
from Persistence import Persistent
import BTree

class ChatSession(Persistent):

    """Class for a chat session.
    Messages are stored in a B-tree, indexed by the time the message
    was created.  (Eventually we'd want to throw messages out,

    add_message(message) -- add a message to the channel
    new_messages()       -- return new messages since the last call to
                            this method
    
    
    """

    def __init__(self, name):
        """Initialize new chat session.
        name -- the channel's name
        """

        self.name = name

        # Internal attribute: _messages holds all the chat messages.        
        self._messages = BTree.BTree()
        

    def new_messages(self):
        "Return new messages."

        # self._v_last_time is the time of the most recent message
        # returned to the user of this class. 
        if not hasattr(self, '_v_last_time'):
            self._v_last_time = 0

        new = []
        T = self._v_last_time

        for T2, message in self._messages.items():
            if T2 > T:
                new.append( message )
                self._v_last_time = T2

        return new
    
    def add_message(self, message):
        """Add a message to the channel.
        message -- text of the message to be added
        """

        while 1:
            try:
                now = time.time()
                self._messages[ now ] = message
                get_transaction().commit()
            except ConflictError:
                # Conflict occurred; this process should pause and
                # wait for a little bit, then try again.
                time.sleep(.2)
                pass
            else:
                # No ConflictError exception raised, so break
                # out of the enclosing while loop.
                break
        # end while

def get_chat_session(conn, channelname):
    """Return the chat session for a given channel, creating the session
    if required."""

    # We'll keep a B-tree of sessions, mapping channel names to
    # session objects.  The B-tree is stored at the ZODB's root under
    # the key 'chat_sessions'.
    root = conn.root()
    if not root.has_key('chat_sessions'):
        print 'Creating chat_sessions B-tree'
        root['chat_sessions'] = BTree.BTree()
        get_transaction().commit()
        
    sessions = root['chat_sessions']

    # Get a session object corresponding to the channel name, creating
    # it if necessary.
    if not sessions.has_key( channelname ):
        print 'Creating new session:', channelname
        sessions[ channelname ] = ChatSession(channelname)
        get_transaction().commit()

    session = sessions[ channelname ]
    return session
    

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print 'Usage: %s <channelname>' % sys.argv[0]
        sys.exit(0)

    storage = ClientStorage.ClientStorage( ('localhost', 9672) )
    db = ZODB.DB( storage )
    conn = db.open()

    s = session = get_chat_session(conn, sys.argv[1])

    messages = ['Hi.', 'Hello', 'Me too', "I'M 3L33T!!!!"]

    while 1:
        # Send a random message
        msg = random.choice(messages)
        session.add_message( '%s: pid %i' % (msg,os.getpid() ))

        # Display new messages
        for msg in session.new_messages():
            print msg

        # Wait for a few seconds
        pause = random.randint( 1, 4 ) 
        time.sleep( pause )
        


=== Added File StandaloneZODB/Doc/guide/gfdl.tex ===
% gfdl.tex 
% This file is a chapter.  It must be included in a larger document to work
% properly.

\section{GNU Free Documentation License}

Version 1.1, March 2000\\

 Copyright $\copyright$ 2000  Free Software Foundation, Inc.\\
     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\\
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

\subsection*{Preamble}

The purpose of this License is to make a manual, textbook, or other
written document ``free'' in the sense of freedom: to assure everyone
the effective freedom to copy and redistribute it, with or without
modifying it, either commercially or noncommercially.  Secondarily,
this License preserves for the author and publisher a way to get
credit for their work, while not being considered responsible for
modifications made by others.

This License is a kind of ``copyleft'', which means that derivative
works of the document must themselves be free in the same sense.  It
complements the GNU General Public License, which is a copyleft
license designed for free software.

We have designed this License in order to use it for manuals for free
software, because free software needs free documentation: a free
program should come with manuals providing the same freedoms that the
software does.  But this License is not limited to software manuals;
it can be used for any textual work, regardless of subject matter or
whether it is published as a printed book.  We recommend this License
principally for works whose purpose is instruction or reference.

\subsection{Applicability and Definitions}

This License applies to any manual or other work that contains a
notice placed by the copyright holder saying it can be distributed
under the terms of this License.  The ``Document'', below, refers to any
such manual or work.  Any member of the public is a licensee, and is
addressed as ``you''.

A ``Modified Version'' of the Document means any work containing the
Document or a portion of it, either copied verbatim, or with
modifications and/or translated into another language.

A ``Secondary Section'' is a named appendix or a front-matter section of
the Document that deals exclusively with the relationship of the
publishers or authors of the Document to the Document's overall subject
(or to related matters) and contains nothing that could fall directly
within that overall subject.  (For example, if the Document is in part a
textbook of mathematics, a Secondary Section may not explain any
mathematics.)  The relationship could be a matter of historical
connection with the subject or with related matters, or of legal,
commercial, philosophical, ethical or political position regarding
them.

The ``Invariant Sections'' are certain Secondary Sections whose titles
are designated, as being those of Invariant Sections, in the notice
that says that the Document is released under this License.

The ``Cover Texts'' are certain short passages of text that are listed,
as Front-Cover Texts or Back-Cover Texts, in the notice that says that
the Document is released under this License.

A ``Transparent'' copy of the Document means a machine-readable copy,
represented in a format whose specification is available to the
general public, whose contents can be viewed and edited directly and
straightforwardly with generic text editors or (for images composed of
pixels) generic paint programs or (for drawings) some widely available
drawing editor, and that is suitable for input to text formatters or
for automatic translation to a variety of formats suitable for input
to text formatters.  A copy made in an otherwise Transparent file
format whose markup has been designed to thwart or discourage
subsequent modification by readers is not Transparent.  A copy that is
not ``Transparent'' is called ``Opaque''.

Examples of suitable formats for Transparent copies include plain
ASCII without markup, Texinfo input format, \LaTeX~input format, SGML
or XML using a publicly available DTD, and standard-conforming simple
HTML designed for human modification.  Opaque formats include
PostScript, PDF, proprietary formats that can be read and edited only
by proprietary word processors, SGML or XML for which the DTD and/or
processing tools are not generally available, and the
machine-generated HTML produced by some word processors for output
purposes only.

The ``Title Page'' means, for a printed book, the title page itself,
plus such following pages as are needed to hold, legibly, the material
this License requires to appear in the title page.  For works in
formats which do not have any title page as such, ``Title Page'' means
the text near the most prominent appearance of the work's title,
preceding the beginning of the body of the text.


\subsection{Verbatim Copying}

You may copy and distribute the Document in any medium, either
commercially or noncommercially, provided that this License, the
copyright notices, and the license notice saying this License applies
to the Document are reproduced in all copies, and that you add no other
conditions whatsoever to those of this License.  You may not use
technical measures to obstruct or control the reading or further
copying of the copies you make or distribute.  However, you may accept
compensation in exchange for copies.  If you distribute a large enough
number of copies you must also follow the conditions in section 3.

You may also lend copies, under the same conditions stated above, and
you may publicly display copies.


\subsection{Copying in Quantity}

If you publish printed copies of the Document numbering more than 100,
and the Document's license notice requires Cover Texts, you must enclose
the copies in covers that carry, clearly and legibly, all these Cover
Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on
the back cover.  Both covers must also clearly and legibly identify
you as the publisher of these copies.  The front cover must present
the full title with all words of the title equally prominent and
visible.  You may add other material on the covers in addition.
Copying with changes limited to the covers, as long as they preserve
the title of the Document and satisfy these conditions, can be treated
as verbatim copying in other respects.

If the required texts for either cover are too voluminous to fit
legibly, you should put the first ones listed (as many as fit
reasonably) on the actual cover, and continue the rest onto adjacent
pages.

If you publish or distribute Opaque copies of the Document numbering
more than 100, you must either include a machine-readable Transparent
copy along with each Opaque copy, or state in or with each Opaque copy
a publicly-accessible computer-network location containing a complete
Transparent copy of the Document, free of added material, which the
general network-using public has access to download anonymously at no
charge using public-standard network protocols.  If you use the latter
option, you must take reasonably prudent steps, when you begin
distribution of Opaque copies in quantity, to ensure that this
Transparent copy will remain thus accessible at the stated location
until at least one year after the last time you distribute an Opaque
copy (directly or through your agents or retailers) of that edition to
the public.

It is requested, but not required, that you contact the authors of the
Document well before redistributing any large number of copies, to give
them a chance to provide you with an updated version of the Document.


\subsection{Modifications}

You may copy and distribute a Modified Version of the Document under
the conditions of sections 2 and 3 above, provided that you release
the Modified Version under precisely this License, with the Modified
Version filling the role of the Document, thus licensing distribution
and modification of the Modified Version to whoever possesses a copy
of it.  In addition, you must do these things in the Modified Version:

\begin{itemize}

\item Use in the Title Page (and on the covers, if any) a title distinct
   from that of the Document, and from those of previous versions
   (which should, if there were any, be listed in the History section
   of the Document).  You may use the same title as a previous version
   if the original publisher of that version gives permission.
\item List on the Title Page, as authors, one or more persons or entities
   responsible for authorship of the modifications in the Modified
   Version, together with at least five of the principal authors of the
   Document (all of its principal authors, if it has less than five).
\item State on the Title page the name of the publisher of the
   Modified Version, as the publisher.
\item Preserve all the copyright notices of the Document.
\item Add an appropriate copyright notice for your modifications
   adjacent to the other copyright notices.
\item Include, immediately after the copyright notices, a license notice
   giving the public permission to use the Modified Version under the
   terms of this License, in the form shown in the Addendum below.
\item Preserve in that license notice the full lists of Invariant Sections
   and required Cover Texts given in the Document's license notice.
\item Include an unaltered copy of this License.
\item Preserve the section entitled ``History'', and its title, and add to
   it an item stating at least the title, year, new authors, and
   publisher of the Modified Version as given on the Title Page.  If
   there is no section entitled ``History'' in the Document, create one
   stating the title, year, authors, and publisher of the Document as
   given on its Title Page, then add an item describing the Modified
   Version as stated in the previous sentence.
\item Preserve the network location, if any, given in the Document for
   public access to a Transparent copy of the Document, and likewise
   the network locations given in the Document for previous versions
   it was based on.  These may be placed in the ``History'' section.
   You may omit a network location for a work that was published at
   least four years before the Document itself, or if the original
   publisher of the version it refers to gives permission.
\item In any section entitled ``Acknowledgements'' or ``Dedications'',
   preserve the section's title, and preserve in the section all the
   substance and tone of each of the contributor acknowledgements
   and/or dedications given therein.
\item Preserve all the Invariant Sections of the Document,
   unaltered in their text and in their titles.  Section numbers
   or the equivalent are not considered part of the section titles.
\item Delete any section entitled ``Endorsements''.  Such a section
   may not be included in the Modified Version.
\item Do not retitle any existing section as ``Endorsements''
   or to conflict in title with any Invariant Section.

\end{itemize}

If the Modified Version includes new front-matter sections or
appendices that qualify as Secondary Sections and contain no material
copied from the Document, you may at your option designate some or all
of these sections as invariant.  To do this, add their titles to the
list of Invariant Sections in the Modified Version's license notice.
These titles must be distinct from any other section titles.

You may add a section entitled ``Endorsements'', provided it contains
nothing but endorsements of your Modified Version by various
parties -- for example, statements of peer review or that the text has
been approved by an organization as the authoritative definition of a
standard.

You may add a passage of up to five words as a Front-Cover Text, and a
passage of up to 25 words as a Back-Cover Text, to the end of the list
of Cover Texts in the Modified Version.  Only one passage of
Front-Cover Text and one of Back-Cover Text may be added by (or
through arrangements made by) any one entity.  If the Document already
includes a cover text for the same cover, previously added by you or
by arrangement made by the same entity you are acting on behalf of,
you may not add another; but you may replace the old one, on explicit
permission from the previous publisher that added the old one.

The author(s) and publisher(s) of the Document do not by this License
give permission to use their names for publicity for or to assert or
imply endorsement of any Modified Version.


\subsection{Combining Documents}

You may combine the Document with other documents released under this
License, under the terms defined in section 4 above for modified
versions, provided that you include in the combination all of the
Invariant Sections of all of the original documents, unmodified, and
list them all as Invariant Sections of your combined work in its
license notice.

The combined work need only contain one copy of this License, and
multiple identical Invariant Sections may be replaced with a single
copy.  If there are multiple Invariant Sections with the same name but
different contents, make the title of each such section unique by
adding at the end of it, in parentheses, the name of the original
author or publisher of that section if known, or else a unique number.
Make the same adjustment to the section titles in the list of
Invariant Sections in the license notice of the combined work.

In the combination, you must combine any sections entitled ``History''
in the various original documents, forming one section entitled
``History''; likewise combine any sections entitled ``Acknowledgements'',
and any sections entitled ``Dedications''.  You must delete all sections
entitled ``Endorsements.''


\subsection{Collections of Documents}

You may make a collection consisting of the Document and other documents
released under this License, and replace the individual copies of this
License in the various documents with a single copy that is included in
the collection, provided that you follow the rules of this License for
verbatim copying of each of the documents in all other respects.

You may extract a single document from such a collection, and distribute
it individually under this License, provided you insert a copy of this
License into the extracted document, and follow this License in all
other respects regarding verbatim copying of that document.



\subsection{Aggregation With Independent Works}

A compilation of the Document or its derivatives with other separate
and independent documents or works, in or on a volume of a storage or
distribution medium, does not as a whole count as a Modified Version
of the Document, provided no compilation copyright is claimed for the
compilation.  Such a compilation is called an ``aggregate'', and this
License does not apply to the other self-contained works thus compiled
with the Document, on account of their being thus compiled, if they
are not themselves derivative works of the Document.

If the Cover Text requirement of section 3 is applicable to these
copies of the Document, then if the Document is less than one quarter
of the entire aggregate, the Document's Cover Texts may be placed on
covers that surround only the Document within the aggregate.
Otherwise they must appear on covers around the whole aggregate.


\subsection{Translation}

Translation is considered a kind of modification, so you may
distribute translations of the Document under the terms of section 4.
Replacing Invariant Sections with translations requires special
permission from their copyright holders, but you may include
translations of some or all Invariant Sections in addition to the
original versions of these Invariant Sections.  You may include a
translation of this License provided that you also include the
original English version of this License.  In case of a disagreement
between the translation and the original English version of this
License, the original English version will prevail.


\subsection{Termination}

You may not copy, modify, sublicense, or distribute the Document except
as expressly provided for under this License.  Any other attempt to
copy, modify, sublicense or distribute the Document is void, and will
automatically terminate your rights under this License.  However,
parties who have received copies, or rights, from you under this
License will not have their licenses terminated so long as such
parties remain in full compliance.


\subsection{Future Revisions of This Licence}

The Free Software Foundation may publish new, revised versions
of the GNU Free Documentation License from time to time.  Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns. See
\url{http://www.gnu.org/copyleft/}.

Each version of the License is given a distinguishing version number.
If the Document specifies that a particular numbered version of this
License "or any later version" applies to it, you have the option of
following the terms and conditions either of that specified version or
of any later version that has been published (not as a draft) by the
Free Software Foundation.  If the Document does not specify a version
number of this License, you may choose any version ever published (not
as a draft) by the Free Software Foundation.

\subsection*{ADDENDUM: How to use this License for your documents}

To use this License in a document you have written, include a copy of
the License in the document and put the following copyright and
license notices just after the title page:

\begin{quote}

      Copyright $\copyright$  YEAR  YOUR NAME.
      Permission is granted to copy, distribute and/or modify this document
      under the terms of the GNU Free Documentation License, Version 1.1
      or any later version published by the Free Software Foundation;
      with the Invariant Sections being LIST THEIR TITLES, with the
      Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.
      A copy of the license is included in the section entitled ``GNU
      Free Documentation License''.

\end{quote}

If you have no Invariant Sections, write ``with no Invariant Sections''
instead of saying which ones are invariant.  If you have no
Front-Cover Texts, write ``no Front-Cover Texts'' instead of
``Front-Cover Texts being LIST''; likewise for Back-Cover Texts.

If your document contains nontrivial examples of program code, we
recommend releasing these examples in parallel under your choice of
free software license, such as the GNU General Public License,
to permit their use in free software.



=== Added File StandaloneZODB/Doc/guide/indexing.tex ===

% Indexing Data
%    BTrees
%    Full-text indexing



=== Added File StandaloneZODB/Doc/guide/introduction.tex ===

%Introduction
%   What is ZODB?
%   What is ZEO?
%   OODBs vs. Relational DBs
%   Other OODBs

\section{Introduction}

This guide explains how to write Python programs that use the Z Object
Database (ZODB) and Zope Enterprise Objects (ZEO).

\subsection{What is the ZODB?}

The ZODB is a persistence system for Python objects.  Persistent
programming languages provide facilities that automatically write
objects to disk and read them in again when they're required by a
running program.  By installing the ZODB, you add such facilities to
Python.

It's certainly possible to build your own system for making Python
objects persistent.  The usual starting points are the \module{pickle}
module, for converting objects into a string representation, and
various database modules, such as the \module{gdbm} or \module{bsddb}
modules, that provide ways to write strings to disk and read them
back.  It's straightforward to combine the \module{pickle} module and
a database module to store and retrieve objects, and in fact the
\module{shelve} module, included in Python's standard library, does
this.

The downside is that the programmer has to explicitly manage objects,
reading an object when it's needed and writing it out to disk when the
object is no longer required.  The ZODB manages objects for you,
keeping them in a cache and writing them out if they haven't been 
accessed in a while.  


\subsection{OODBs vs. Relational DBs}

Another way to look at it is that the ZODB is a Python-specific
object-oriented database (OODB).  Commercial object databases for C++
or Java often require that you jump through some hoops, such as using
a special preprocessor or avoiding certain data types.  As we'll see,
the ZODB has some hoops of its own to jump through, but in comparison
the naturalness of the ZODB is astonishing.

Relational databases (RDBs) are far more common than OODBs.
Relational databases store information in tables; a table consists of
any number of rows, each row containing several columns of
information.  (Rows are more formally called relations, which is where
the term ``relational database'' originates.)

Let's look at a concrete example.  The example comes from my day job
working for the MEMS Exchange, in a greatly simplified version.  The
job is to track process runs, which are lists of manufacturing steps
to be performed in a semiconductor fab.  A run is owned by a
particular user, and has a name and assigned ID number.  Runs consist
of a number of operations; an operation is a single step to be
performed, such as depositing something on a wafer or etching
something off it.

Operations may have parameters, which are additional information
required to perform an operation.  For example, if you're depositing
something on a wafer, you need to know two things: 1) what you're
depositing, and 2) how much should be deposited.  You might deposit
100 microns of silicon oxide, or 1 micron of copper.

Mapping these structures to a relational database is straightforward:

\begin{verbatim}
CREATE TABLE runs (
  int      run_id,
  varchar  owner,
  varchar  title,
  int      acct_num,
  primary key(run_id)
);

CREATE TABLE operations (
  int      run_id,
  int      step_num, 
  varchar  process_id,
  PRIMARY KEY(run_id, step_num),
  FOREIGN KEY(run_id) REFERENCES runs(run_id),
);

CREATE TABLE parameters (
  int      run_id,
  int      step_num, 
  varchar  param_name, 
  varchar  param_value,
  PRIMARY KEY(run_id, step_num, param_name)
  FOREIGN KEY(run_id, step_num) 
     REFERENCES operations(run_id, step_num),
);  
\end{verbatim}

In Python, you would write three classes named \class{Run},
\class{Operation}, and \class{Parameter}.  I won't present code for
defining these classes, since that code is uninteresting at this
point. Each class would contain a single method to begin with, an
\method{__init__} method that assigns default values, such as 0 or
\code{None}, to each attribute of the class.

It's not difficult to write Python code that will create a \class{Run}
instance and populate it with the data from the relational tables;
with a little more effort, you can build a straightforward tool,
usually called an object-relational mapper, to do this automatically.
(See
\url{http://www.amk.ca/python/unmaintained/ordb.html} for a quick hack
at a Python object-relational mapper, and
\url{http://www.python.org/workshops/1997-10/proceedings/shprentz.html}
for Joel Shprentz's more successful implementation of the same idea;
Unlike mine, Shprentz's system has been used for actuual work.)

However, it is difficult to make an object-relational mapper
reasonably quick; a simple-minded implementation like mine is quite
slow because it has to do several queries to access all of an object's
data.  Higher performance object-relational mappers cache objects to
improve performance, only performing SQL queries when they actually
need to.

That helps if you want to access run number 123 all of a sudden.  But
what if you want to find all runs where a step has a parameter named
'thickness' with a value of 2.0?  In the relational version, you have
two unappealing choices:

\begin{enumerate}
 \item Write a specialized SQL query for this case: \code{SELECT run_id
  FROM operations WHERE param_name = 'thickness' AND param_value = 2.0}
  
  If such queries are common, you can end up with lots of specialized
  queries.  When the database tables get rearranged, all these queries
  will need to be modified.

 \item An object-relational mapper doesn't help much.  Scanning
  through the runs means that the the mapper will perform the required
  SQL queries to read run \#1, and then a simple Python loop can check
  whether any of its steps have the parameter you're looking for.
  Repeat for run \#2, 3, and so forth.  This does a vast
  number of SQL queries, and therefore is incredibly slow.

\end{enumerate}

An object database such as ZODB simply stores internal pointers from
object to object, so reading in a single object is much faster than
doing a bunch of SQL queries and assembling the results. Scanning all
runs, therefore, is still inefficient, but not grossly inefficient.

\subsection{What is ZEO?}

The ZODB comes with a few different classes that implement the
\class{Storage} interface.  Such \class{*Storage} classes handle the job of
writing out Python objects to a physical storage medium, which can be
a disk file (the \class{FileStorage} class), a BerkeleyDB file
(\class{BerkeleyStorage}), a relational database
(\class{DCOracleStorage}), or some other medium.  ZEO adds
\class{ClientStorage}, a new \class{Storage} that doesn't write to
physical media but just forwards all requests across a network to a
server.  The server, which is running an instance of the
\class{StorageServer} class, simply acts as a front-end for some
physical \class{Storage} class.  It's a fairly simple idea, but as
we'll see later on in this document, it opens up many possibilities.

\subsection{About this guide}

The primary author of this guide works on a project which uses the
ZODB and ZEO as its primary storage technology.  We use the ZODB to
store process runs and operations, a catalog of available processes,
user information, accounting information, and other data.  Part of the
goal of writing this document is to make our experience more widely
available.  A few times we've spent hours or even days trying to
figure out a problem, and this guide is an attempt to gather up the
knowledge we've gained so that others don't have to make the same
mistakes we did while learning.

This document will always be a work in progress.  If you wish to
suggest clarifications or additional topics, please send your comments to
\email{akuchlin@mems-exchange.org}.

\subsection{Acknowledgements}

I'd like to thank the people who've pointed out inaccuracies and bugs,
offered suggestions on the text, or proposed new topics that should be
covered: Jeff Bauer, Willem Broekema, Chris McDonough, George Runyan.


=== Added File StandaloneZODB/Doc/guide/links.tex ===
% links.tex
% Collection of relevant links

\section{Resources}

ZODB HOWTO, by Michel Pelletier:
\\
Goes into slightly more detail about the rules for writing applications using the ZODB.
\\
\url{http://www.zope.org/Members/michel/HowTos/ZODB-How-To}


Introduction to the Zope Object Database, by Jim Fulton:
\\
Goes into much greater detail, explaining advanced uses of the ZODB and 
how it's actually implemented.  A definitive reference, and highly recommended.
\\
\url{http://www.python.org/workshops/2000-01/proceedings/papers/fulton/zodb3.html}

Download link for ZEO: \\
\url{http://www.zope.org/Products/ZEO/}




=== Added File StandaloneZODB/Doc/guide/modules.tex ===

% Related Modules
%    PersistentMapping
%    PersistentList
%    BTree
%    Catalog

\section{Related Modules}

The ZODB package includes a number of related modules that provide
useful data types such as BTrees or full-text indexes.

\subsection{\module{ZODB.PersistentMapping}}

The \class{PersistentMapping} class is a wrapper for mapping objects
that will set the dirty bit when the mapping is modified by setting or
deleting a key.  

\begin{funcdesc}{PersistentMapping}{container = \{\}}
Create a \class{PersistentMapping} object that wraps the 
mapping object \var{container}.  If you don't specify a
value for \var{container}, a regular Python dictionary is used.
\end{funcdesc}

\class{PersistentMapping} objects support all the same methods as 
Python dictionaries do.

\subsection{\module{ZODB.PersistentList}}

The \class{PersistentList} class is a wrapper for mutable sequence objects, 
much as \class{PersistentMapping} is a wrapper for mappings.  

\begin{funcdesc}{PersistentList}{initlist = []}
Create a \class{PersistentList} object that wraps the 
mutable sequence object \var{initlist}.  If you don't specify a
value for \var{initlist}, a regular Python list is used.
\end{funcdesc}

\class{PersistentList} objects support all the same methods as 
Python lists do.


\subsection{B-tree Modules}

%here's one: how does one implement searching? i would have expected the
%btree objects to have ``find key nearest to this'' and ``next'' methods,
%(like bsddb's set_location)...
%
%  -- erno

When programming with the ZODB, Python dictionaries aren't always what
you need.  The most important case is where you want to store a very
large mapping.  When a Python dictionary is accessed in a ZODB, the
whole dictionary has to be unpickled and brought into memory.  If
you're storing something very large, such as a 100,000-entry user
database, unpickling such a large object will be slow.  B-trees are a
balanced tree data structure that behave like a mapping but distribute
keys throughout a number of tree nodes.  Nodes are then only unpickled
and brought into memory as they're accessed, so the entire tree
doesn't have to occupy memory (unless you really are touching every
single key).

There are four different BTree modules provided.  One of them, the
\module{BTree} module, provides the most general data type; the keys
and values in the B-tree can be any Python object.  Some specialized B-tree
modules require that the keys, and perhaps even the values, to be of a
certain type, and provide faster performance because of this limitation.

\begin{itemize}
\item[ \module{IOBTree} ] requires the keys to be integers.
The module name reminds you of this; the \module{IOBTree} module
maps Integers to Objects.

\item[ \module{OIBTree} ] requires the values to be integers,
mapping Objects to Integers.

\item[ \module{IIBTree} ] is strictest, requiring that both keys and values must be integers.

\end{itemize}

To use a B-tree, simply import the desired module and call the
constructor, always named \function{BTree()}, to get a B-tree
instance, and then use it like any other mapping:

\begin{verbatim}
import IIBTree
iimap = IIBTree.BTree()
iimap[1972] = 27
\end{verbatim}



=== Added File StandaloneZODB/Doc/guide/prog-zodb.tex === (465/565 lines abridged)

%ZODB Programming
%   How ZODB works (ExtensionClass, dirty bits)
%   Installing ZODB
%   Rules for Writing Persistent Classes
   

\section{ZODB Programming}

\subsection{Installing ZODB}

The ZODB forms part of Zope, but it's difficult and somewhat painful
to extract the bits from Zope needed to support just the ZODB.
Therefore I've assembled a distribution containing only the packages
required to use the ZODB and ZEO, so you can install it and start
programming right away.

To download the distribution, go to my ZODB page at 
\url{http://www.amk.ca/zodb/}.  
The distribution is still experimental, so don't be surprised if the
installation process runs into problems.  Please inform me of any
difficulties you encounter.

\subsubsection{Requirements}

You'll need Python, of course; version 1.5.2 works with some fixes,
and it also works with Python 2.0, which is what I primarily use.

The code is packaged using Distutils, the new distribution tools for
Python introduced in Python 2.0.  If you're using 1.5.2, first you'll
have to get the latest Distutils release from the Distutils SIG page
at \url{http://www.python.org/sigs/distutils-sig/download.html} and
install it.  This is simply a matter of untarring or unzipping the
release package, and then running \code{python setup.py install}.

If you're using 1.5.2 and have installed previous versions of the
Distutils, be sure to get the very latest version, since developing
the ZODB distribution turned up some bugs along the way.  If you
encounter problems compiling \file{ZODB/TimeStamp.c} or your compiler reports
an error like ``Can't create build/temp.linux2/ExtensionClass.o: No
such file or directory'', you need an updated version.  Old versions of
Distutils have two bugs which affect the setup scripts.  First, for a
long time the \code{define_macros} keyword in setup.py files didn't work due
to a Distutils bug, so I hacked TimeStamp.c in earlier releases.  The
Distutils have since been fixed, and the hack became unnecessary, so I
removed it.  Second, the code that creates directories tries to be
smart and caches them to save time by not trying to create a directory
twice, but this code was broken in old versions.

You'll need a C compiler to build the packages, because there are

[-=- -=- -=- 465 lines omitted -=- -=- -=-]

automatically detecting changes to the object is disabled while the
\method{__getattr__}, \method{__delattr__}, or \method{__setattr__}
method is executing.  This means that if the object is modified, the
object should be marked as dirty by setting the object's
\member{_p_changed} method to true.

\subsection{Writing Persistent Classes}

Now that we've looked at the basics of programming using the ZODB,
we'll turn to some more subtle tasks that are likely to come up for
anyone using the ZODB in a production system.

\subsubsection{Changing Instance Attributes}

Ideally, before making a class persistent you would get its interface
right the first time, so that no attributes would ever need to be
added, removed, or have their interpretation change over time.  It's a
worthy goal, but also an impractical one unless you're gifted with
perfect knowledge of the future.  Such unnatural foresight can't be
required of any person, so you therefore have to be prepared to handle
such structural changes gracefully.  In object-oriented database
terminology, this is a schema update.  The ZODB doesn't have an actual
schema specification, but you're changing the software's expectations
of the data contained by an object, so you're implicitly changing the
schema.

One way to handle such a change is to write a one-time conversion
program that will loop over every single object in the database and
update them to match the new schema.  This can be easy if your network
of object references is quite structured, making it easy to find all
the instances of the class being modified.  For example, if all
\class{User} objects can be found inside a single dictionary or
B-tree, then it would be a simple matter to loop over every
\class{User} instance with a \keyword{for} statement.
This is more difficult if your object graph is less structured; if
\class{User} objects can be found as attributes of any number of
different class instances, then there's no longer any easy way to find
them all, short of writing a generalized object traversal function
that would walk over every single object in a ZODB, checking each one
to see if it's an instance of \class{User}.  
\footnote{XXX is there a convenience method for walking the object graph hiding
somewhere inside DC's code?  Should there be a utility method for
doing this?  Should I write one and include it in this section?}
Some OODBs support a feature called extents, which allow quickly
finding all the instances of a given class, no matter where they are
in the object graph; unfortunately the ZODB doesn't offer extents as a
feature.

XXX Rest of section not written yet: __getstate__/__setstate__



=== Added File StandaloneZODB/Doc/guide/storages.tex ===

% Storages
%    FileStorage
%    BerkeleyStorage
%    OracleStorage

\section{Storages}

This chapter will examine the different \class{Storage} subclasses
that are considered stable, discuss their varying characteristics, and
explain how to administer them.

\subsection{Using Multiple Storages}

XXX explain mounting substorages

\subsection{FileStorage}

\subsection{BerkeleyStorage}

\subsection{OracleStorage}



=== Added File StandaloneZODB/Doc/guide/transactions.tex ===
%Transactions and Versioning
%   Committing and Aborting
%   Subtransactions
%   Undoing
%   Versions
%   Multithreaded ZODB Programs
   

\section{Transactions and Versioning}

%\subsection{Committing and Aborting}
% XXX There seems very little to say about commit/abort...

\subsection{Subtransactions}

Subtransactions can be created within a transaction.  Each
subtransaction can be individually committed and aborted, but the
changes within a subtransaction are not truly committed until the
containing transaction is committed.

The primary purpose of subtransactions is to decrease the memory usage
of transactions that touch a very large number of objects.  Consider a
transaction during which 200,000 objects are modified.  All the
objects that are modified in a single transaction have to remain in
memory until the transaction is committed, because the ZODB can't
discard them from the object cache.  This can potentially make the
memory usage quite large.  With subtransactions, a commit can be be
performed at intervals, say, every 10,000 objects.  Those 10,000
objects are then written to permanent storage and can be purged from
the cache to free more space.

To commit a subtransaction instead of a full transaction, 
pass a true value to the \method{commit()}
or \method{abort()} method of the \class{Transaction} object.

\begin{verbatim}
# Commit a subtransaction
get_transaction().commit(1)	

# Abort a subtransaction
get_transaction().abort(1)   
\end{verbatim}

A new subtransaction is automatically started on committing or
aborting the previous subtransaction.


\subsection{Undoing Changes}

Some types of \class{Storage} support undoing a transaction even after
it's been committed.  You can tell if this is the case by calling the
\method{supportsUndo()} method of the \class{DB} instance, which
returns true if the underlying storage supports undo.  Alternatively
you can call the \method{supportsUndo()} method on the underlying
storage instance.

If a database supports undo, then the \method{undoLog(\var{start},
\var{end}\optional{, func})} method on the \class{DB} instance returns
the log of past transactions, returning transactions between the times
\var{start} and \var{end}, measured in seconds from the epoch.   
If present, \var{func} is a function that acts as a filter on the
transactions to be returned; it's passed a dictionary representing
each transaction, and only transactions for which \var{func} returns
true will be included in the list of transactions returned to the
caller of \method{undoLog()}.  The dictionary contains keys for
various properties of the transaction.  The most important keys are
\samp{id}, for the transaction ID, and \samp{time}, for the time at
which the transaction was committed.  

\begin{verbatim}
>>> print storage.undoLog(0, sys.maxint)
[{'description': '',
  'id': 'AzpGEGqU/0QAAAAAAAAGMA',
  'time': 981126744.98,
  'user_name': ''},
 {'description': '',
  'id': 'AzpGC/hUOKoAAAAAAAAFDQ',
  'time': 981126478.202,
  'user_name': ''}
  ...
\end{verbatim}

To store a description and a user name on a commit, get the current
transaction and call the \method{note(\var{text})} method to store a
description, and the
\method{setUser(\var{user_name})} method to store the user name.  
While \method{setUser()} overwrites the current user name and replaces
it with the new value, the \method{note()} method always adds the text
to the transaction's description, so it can be called several times to
log several different changes made in the course of a single
transaction.

\begin{verbatim}
get_transaction().setUser('amk')
get_transaction().note('Change ownership')
\end{verbatim}

To undo a transaction, call the \method{DB.undo(\var{id})} method,
passing it the ID of the transaction to undo.  If the transaction
can't be undone, a \exception{ZODB.POSException.UndoError} exception
will be raised, with the message ``non-undoable
transaction''.  Usually this will happen because later transactions
modified the objects affected by the transaction you're trying to
undo.

\subsection{Versions}

While many subtransactions can be contained within a single regular
transaction, it's also possible to contain many regular transactions
within a long-running transaction, called a version in ZODB
terminology.  Inside a version, any number of transactions can be
created and committed or rolled back, but the changes within a version
are not made visible to other connections to the same ZODB.

Not all storages support versions, but you can test for versioning
ability by calling \method{supportsVersions()} method of the
\class{DB} instance, which returns true if the underlying storage
supports versioning.

A version can be selected when creating the \class{Connection}
instance using the \method{DB.open(\optional{\var{version}})} method.
The \var{version} argument must be a string that will be used as the
name of the version.

\begin{verbatim}
vers_conn = db.open(version='Working version')
\end{verbatim}

Transactions can then be committed and aborted using this versioned
connection.  Other connections that don't specify a version, or
provide a different version name, will not see changes committed
within the version named \samp{Working~version}.  To commit or abort a
version, which will either make the changes visible to all clients or
roll them back, call the \method{DB.commitVersion()} or
\method{DB.abortVersion()} methods.
XXX what are the source and dest arguments for?

The ZODB makes no attempt to reconcile changes between different
versions.  Instead, the first version which modifies an object will
gain a lock on that object.  Attempting to modify the object from a
different version or from an unversioned connection will cause a
\exception{ZODB.POSException.VersionLockError} to be raised:

\begin{verbatim}
from ZODB.POSException import VersionLockError

try:
    get_transaction().commit()
except VersionLockError, (obj_id, version):
    print ('Cannot commit; object %s '
           'locked by version %s' % (obj_id, version) )
\end{verbatim}

The exception provides the ID of the locked object, and the name of
the version having a lock on it.

\subsection{Multithreaded ZODB Programs}

ZODB databases can be accessed from multithreaded Python programs.
The \class{Storage} and \class{DB} instances can be shared among
several threads, as long as individual \class{Connection} instances
are created for each thread.  

XXX I can't think of anything else to say about multithreaded ZODB
programs.  Suggestions?  An example program?



=== Added File StandaloneZODB/Doc/guide/zeo.tex ===

% ZEO
%    Installing ZEO
%    How ZEO works (ClientStorage)
%    Configuring ZEO
   
\section{ZEO}
\label{zeo}

\subsection{How ZEO Works}

The ZODB, as I've described it so far, can only be used within a
single Python process (though perhaps with multiple threads).  ZEO,
Zope Enterprise Objects, extends the ZODB machinery to provide access
to objects over a network.  The name "Zope Enterprise Objects" is a
bit misleading; ZEO can be used to store Python objects and access
them in a distributed fashion without Zope ever entering the picture.
The combination of ZEO and ZODB is essentially a Python-specific
object database.

ZEO consists of about 1400 lines of Python code.  The code is
relatively small because it contains only code for a TCP/IP server,
and for a new type of Storage, \class{ClientStorage}.
\class{ClientStorage} doesn't use disk files at all; it simply
makes remote procedure calls to the server, which then passes them on
a regular \class{Storage} class such as \class{FileStorage}.  The
following diagram lays out the system:

XXX insert diagram here later

Any number of processes can create a \class{ClientStorage}
instance, and any number of threads in each process can be using that
instance.  \class{ClientStorage} aggressively caches objects
locally, so in order to avoid using stale data, the ZEO server sends
an invalidate message to all the connected \class{ClientStorage}
instances on every write operation.  The invalidate message contains
the object ID for each object that's been modified, letting the
\class{ClientStorage} instances delete the old data for the
given object from their caches.

This design decision has some consequences you should be aware of.
First, while ZEO isn't tied to Zope, it was first written for use with
Zope, which stores HTML, images, and program code in the database.  As
a result, reads from the database are \emph{far} more frequent than
writes, and ZEO is therefore better suited for read-intensive
applications.  If every \class{ClientStorage} is writing to the
database all the time, this will result in a storm of invalidate
messages being sent, and this might take up more processing time than
the actual database operations themselves.

On the other hand, for applications that have few writes in comparison
to the number of read accesses, this aggressive caching can be a major
win.  Consider a Slashdot-like discussion forum that divides the load
among several Web servers.  If news items and postings are represented
by objects and accessed through ZEO, then the most heavily accessed
objects -- the most recent or most popular postings -- will very
quickly wind up in the caches of the
\class{ClientStorage} instances on the front-end servers.  The
back-end ZEO server will do relatively little work, only being called
upon to return the occasional older posting that's requested, and to
send the occasional invalidate message when a new posting is added.
The ZEO server isn't going to be contacted for every single request,
so its workload will remain manageable.

\subsection{Installing ZEO}

This section covers how to install the ZEO package, and how to 
configure and run a ZEO Storage Server on a machine. 

\subsubsection{Requirements}

To run a ZEO server, you'll need Python 1.5.2 or 2.0, and the ZODB
packages from \url{http://www.amk.ca/files/zodb/}
have to be installed.  

\emph{Note for Python 1.5.2 users}: ZEO requires updated versions
of the \module{asyncore.py} and \module{asynchat.py} modules that are
included in 1.5.2's standard library.  Current versions of the ZODB
distribution install private versions of these modules, so you
shouldn't need to grab updated versions yourself.  (The symptom of
this problem is a traceback on attempting to run a ZEO client program:
the traceback is ``TypeError: too many arguments; expected 2, got 3''
around line 100 of \file{smac.py}.

\subsubsection{Installation}

Installing the ZEO package is easy.  Just run \code{python setup.py
install}.  This will install the ZEO/ package into your Python
installation, and copy various files into their proper locations:
\file{zeo.conf} will be put into \file{/usr/local/etc/}, a \file{zeo} startup
script will be put in \file{/etc/rc.d/init.d/}, and the \file{zeod}
daemon program will be placed in \file{/usr/local/bin}.

\subsection{Configuring and Running a ZEO Server}

Edit \code{/usr/local/etc/zeo.conf} appropriately for your desired
setup.  This configuration file controls the port on which ZEO will
listen for connections, the user and group IDs under which the server
will be executed, and the location of the concrete \class{Storage}
object that will be made network-accessible.
 
\subsection{Testing the ZEO Installation}

Once a ZEO server is up and running, using it is just like using ZODB
with a more conventional disk-based storage; no new programming
details are introduced by using a remote server.  The only difference
is that programs must create a \class{ClientStorage} instance instead
of a \class{FileStorage} instance.  From that point onward, ZODB-based
code is happily unaware that objects are being retrieved from a ZEO
server, and not from the local disk.

As an example, and to test whether ZEO is working correctly, try
running the following lines of code, which will connect to the server,
add some bits of data to the root of the ZODB, and commits the
transaction:

\begin{verbatim}
from ZEO import ClientStorage
from ZODB import DB

# Change next line to connect to your ZEO server
addr = ('kronos.example.com', 1975)
storage = ClientStorage.ClientStorage(addr)
db = DB( storage )
conn = db.open()
root = conn.root()

# Store some things in the root
root['list'] = ['a', 'b', 1.0, 3]
root['dict'] = {'a':1, 'b':4}

# Commit the transaction
get_transaction().commit()
\end{verbatim}

If this code runs properly, then your ZEO server is working correctly.

\subsection{ZEO Programming Notes}

XXX The Connection.sync() method and its necessity (if it works at all!) 

% That doesn't work.  I tested it.  sync() doesn't seem to get into
% the asyncore loop.  One of us should probably look into adding an
% API for this when we have some free time.  It would be a nice
% small project that would get into ZODB's guts.




\subsection{Sample Application: chatter.py}

For an example application, we'll build a little chat application.
What's interesting is that none of the application's code deals with
network programming at all; instead, an object will hold chat
messages, and be magically shared between all the clients through ZEO.
I won't present the complete script here; it's included in my ZODB
distribution, and you can download it from
\url{http://www.amk.ca/zodb/demos/}.  Only the interesting portions of
the code will be covered here.

The basic data structure is the \class{ChatSession} object,
which provides an \method{add_message()} method that adds a
message, and a \method{new_messages()} method that returns a list
of new messages that have accumulated since the last call to
\method{new_messages()}.  Internally, \class{ChatSession}
maintains a B-tree that uses the time as the key, and stores the
message as the corresponding value.

The constructor for \class{ChatSession} is pretty simple; it simply
creates an attribute containing a B-tree:

\begin{verbatim}
class ChatSession(Persistent):
    def __init__(self, name):
        self.name = name
        # Internal attribute: _messages holds all the chat messages.        
        self._messages = BTree.BTree()        
\end{verbatim}

\method{add_message()} has to add a message to the
\code{_messages} B-tree.  A complication is that it's possible
that some other client is trying to add a message at the same time;
when this happens, the client that commits first wins, and the second
client will get a \exception{ConflictError} exception when it tries to
commit.  For this application, \exception{ConflictError} isn't serious
but simply means that the operation has to be retried; other
applications might treat it as a fatal error.  The code uses
\code{try...except...else} inside a \code{while} loop,
breaking out of the loop when the commit works without raising an
exception.

\begin{verbatim}
    def add_message(self, message):
        """Add a message to the channel.
        message -- text of the message to be added
        """

        while 1:
            try:
                now = time.time()
                self._messages[ now ] = message
                get_transaction().commit()
            except ConflictError:
                # Conflict occurred; this process should pause and
                # wait for a little bit, then try again.
                time.sleep(.2)
                pass
            else:
                # No ConflictError exception raised, so break
                # out of the enclosing while loop.
                break
        # end while
\end{verbatim}

\method{new_messages()} introduces the use of \textit{volatile}
attributes.  Attributes of a persistent object that begin with
\code{_v_} are considered volatile and are never stored in the
database.  \method{new_messages()} needs to store the last time
the method was called, but if the time was stored as a regular
attribute, its value would be committed to the database and shared
with all the other clients.  \method{new_messages()} would then
return the new messages accumulated since any other client called
\method{new_messages()}, which isn't what we want.

\begin{verbatim}
    def new_messages(self):
        "Return new messages."

        # self._v_last_time is the time of the most recent message
        # returned to the user of this class. 
        if not hasattr(self, '_v_last_time'):
            self._v_last_time = 0

        new = []
        T = self._v_last_time

        for T2, message in self._messages.items():
            if T2 > T:
                new.append( message )
                self._v_last_time = T2

        return new
\end{verbatim}

This application is interesting because it uses ZEO to easily share a
data structure; ZEO and ZODB are being used for their networking
ability, not primarily for their data storage ability.  I can foresee
many interesting applications using ZEO in this way:

\begin{itemize}
  \item With a Tkinter front-end, and a cleverer, more scalable data
  structure, you could build a shared whiteboard using the same
  technique.

  \item A shared chessboard object would make writing a networked chess
  game easy.  

  \item You could create a Python class containing a CD's title and
  track information.  To make a CD database, a read-only ZEO server
  could be opened to the world, or an HTTP or XML-RPC interface could
  be written on top of the ZODB.

  \item A program like Quicken could use a ZODB on the local disk to
  store its data.  This avoids the need to write and maintain
  specialized I/O code that reads in your objects and writes them out;
  instead you can concentrate on the problem domain, writing objects
  that represent cheques, stock portfolios, or whatever.

\end{itemize}



=== Added File StandaloneZODB/Doc/guide/zodb.dvi ===
÷ƒ’À;    è TeX output 2001.12.17:1422‹                                       ÿÿÿÿ ¨n ý>‘ì £n ý‘ì„   ÕÁHŽŽŸ!ð‘qHó»ló áH 
   phvr7t»ZODB/ZEO›ê™Prog•ÀQr“amming˜GuideŽŸåk’
Ió»ló    
   phvro7t¾Release‘Uü0.02Ž¤*°Ç’}ZÅó»ló ff 
   phvr7tÀA.M.‘ ÈK‘ouchlingŽ¡’‡*Yó3{Ù 
   
   ptmr7t¹December–€ 17,“2001ŽŸ°Ç’Y =akuchlin@mems-eÙ xchange.orÑðgŽŸ!¿üÀContentsŽŸY˜óÓߌ˜ 
   
   ptmb7tÁ1Ž‘  IntrÑðoduction’‹|Q2ŽŽ¤  ‘  ¹1.1Ž‘& What–€ is“the“ZODB?‘Iñ‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 2ŽŽ¡‘  1.2Ž‘& OODBs–€ vs.‘˜Relational“DBs‘f‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C!
 2ŽŽ¡‘  1.3Ž‘& What–€ is“ZEO?‘Iø‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 4ŽŽ¡‘  1.4Ž‘& About–€ this“guideT2‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“!
‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 4ŽŽ¡‘  1.5Ž‘& AcknoÀ wledgements‘Ñ™‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 4ŽŽ©  Á2Ž‘  ZODB‘€ PrÑðogramming’g
4ŽŽ¡‘  ¹2.1Ž‘& Installing‘€ ZODB‘ÿº‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 4ŽŽ¡‘& Requirements‘òù‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘!
ü.ŽŽ‘C 5ŽŽ¡‘& Installing–€ the“PÙ ackages‘ð[‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 5ŽŽ¡‘  2.2Ž‘& HoÀ w–€ ZODB“W‘ÿ37orks‘ºw‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 5ŽŽ¡‘  2.3Ž‘&!
 Opening–€ a“ZODB‘f‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 6ŽŽ¡‘  2.4Ž‘& Writing–€ a“Persistent“Class‘ ã­‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 7ŽŽ¡‘  2.5Ž‘& Rules–€ for“Writing“Pe!
rsistent“Classes‘&‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 8ŽŽ¡‘& Modifying–€ Mutable“ObjectsV¼‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 9ŽŽ¡‘& Some–€ Special“Methods“Don'Ñðt“W‘ÿ37ork‘(|‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.Ž!
Ž“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 9ŽŽ¡‘& Fixing–€ ó0ˆÛ 
   
   pcrr7tÂisinstance“¹and“Âissubclass‘ïØ‘ü¹.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 10ŽŽ¡‘&™š‰  ff €Ž‘,³4‰  ff €Ž‘236Âgetattr‘ ™˜‰  ff €Ž‘³2‰  ff €Ž–34¹,‘˜‰  ff €Ž‘	32‰  ff €Ž›³4Âdelattr‘ ™˜‰  ff €Ž‘³2‰  ff €Ž“¹,‘€ and‘˜‰  ff €Ž‘	32‰  ff €Ž˜Âsetattr‘ ™˜‰  ff €Ž‘³2‰  ff €Ž‘:‘ü¹.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 12ŽŽ¡‘  2.6Ž‘& Writing–€ Persistent“Classes‘ÿÕ‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.!
ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 12ŽŽ¡‘& Changing–€ Instance“AttribÌÐutes‘}‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 12ŽŽ¦Á3Ž‘  ZEO’§¢¡13ŽŽ¡‘  ¹3.1Ž‘& HoÀ w–€ ZEO“W‘ÿ37orks‘'‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘!
ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 13ŽŽ¡‘  3.2Ž‘& Installing‘€ ZEO‘Gj‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 13ŽŽ¡‘& Requirements‘òù‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘!
ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 13ŽŽ¡‘& Installation‘ÿÓ‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 14ŽŽ¡‘  3.3Ž‘& Conguring–€ and“Running“a“ZEO“ServÙ er‘ð ‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.Ž!
Ž“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 14ŽŽ¡‘  3.4Ž‘& T‘ÿLÐesting–€ the“ZEO“Installation‘	î‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 14ŽŽ¡‘  3.5Ž‘& ZEO–€ Programming“Notes‘º‚‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.Ž!
Ž“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 14ŽŽ¡‘  3.6Ž‘& Sample‘€ Application:‘˜chatter‘ÿs8.pægy‘V¸‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 14ŽŽ¦Á4Ž‘  T‘ÿBransactions–€ and“V‘þÿÿersioning’C%}16ŽŽ¡‘  ¹4.1Ž‘& Subtransactions‘«I‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“!
‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 16ŽŽ¡‘  4.2Ž‘& Undoing‘€ Changes‘Žÿ‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 17ŽŽ¡‘  4.3Ž‘& V‘þã×ersions‘(‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ!
“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 17ŽŽ¡‘  4.4Ž‘& Multithreaded–€ ZODB“Programs‘IÛ‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 18ŽŽŽŽŒ‹                                          * ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ìÁ5Ž‘  Related‘€ Modules’tÐÚ18ŽŽ¤  ‘  ¹5.1Ž‘& ÂZODB.PersistentMapping‘F‘ü¹.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“!
‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 18ŽŽ¡‘  5.2Ž‘& ÂZODB.PersistentList‘V‘ü¹.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 19ŽŽ¡‘  5.3Ž‘& B-tree‘€ Modules‘‚P‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“!
‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 19ŽŽ¤  ÁAŽ‘  ResourÑðces’‘19ŽŽ¡BŽ‘  GNU–€ FrÑðee“Documentation“License’*320ŽŽ¤  ‘  ¹B.1Ž‘& Applicability–€ and“Denitions‘Ô/‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 20ŽŽ¡‘  B.2Ž‘& V‘þã×erbatim‘€ Copægying‘ ý$‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“!
‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 21ŽŽ¡‘  B.3Ž‘& Copægying–€ in“Quantity‘^\‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 21ŽŽ¡‘  B.4Ž‘& Modications‘ði‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“!
‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 21ŽŽ¡‘  B.5Ž‘& Combining‘€ Documents‘ðK‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 23ŽŽ¡‘  B.6Ž‘& Collections–€ of“Documents‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘!
ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 23ŽŽ¡‘  B.7Ž‘& AggreÙ gó7ation–€ W™Ÿith“Independent“W‘ÿ37orks‘ÑV‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 23ŽŽ¡‘  B.8Ž‘& T¦granslation‘Ì’‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.Ž!
Ž“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 23ŽŽ¡‘  B.9Ž‘& T‘ÿLÐermination‘BR‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 23ŽŽ¡‘  B.10Ž‘& Future–€ ReÀ visions“of“This“Licence‘m»‘ü.ŽŽ–ø‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘!
ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ“‘ü.ŽŽ‘C 24ŽŽ©°Ç„   ÕÁHŽŽŸ°Ç‘DZž²×cŽŽŽó
!",š 
   
   cmsy10¸
ŽŽŽ‘
 ¹Copšægyright–
2001“A.M.“KÙ uchling.‘ÂIPermission“is“granted“to“cop˜y‘ÿY ,‘0ôdistribÌÐute“and/or“modify“this“document“under“theŽ¡terms–ÉRof“the“GNU‘ÈýFree“Documentation“License,‘¥V‘þã×ersion“1.1“or“anšÙ y“later“v˜ersion“published“by“the“Free“SoftwægareŽ¡FšÙ oundation;‘ØÑwith–eáno“In™ŸvÀ ariant“Sections,‘ŸXno“Front-Co˜v˜er“T‘ÿLÐe˜xts,‘ŸXand“no“Back-Co˜v˜er“T‘ÿLÐe˜xts.‘Ë:A‘e¥copægy“of“the“license“isŽ¡included–€ in“the“appendix“entitled“\GNU“Free“Documentation“License".ŽŸ'pÃÀ1Ž‘hIntroductionŽŸ
_¹This–ÖQguide“eÙ xplains“hoÀ w“to“write“Python“programs“that“use“the“Z›ÕøObject“Database“(ZODB)˜and“Zope“EnterpriseŽ¡Objects‘€ (ZEO).ŽŸ"PÃó»ló    
   phvr7tÄ1.1Ž‘®What–Uüis“the“ZODB?ŽŸpŹThe–‘ZODB‘‘is“a“persistence“system“for“Python“objects.‘L«Persistent“programming“languages“proÙ vide“fægacilities“that“auto-Ž¡matically–µEwrite“objects“to“disk“and“read“them“in“agó7ain“when“theÙ y'‘ÿÿre“required“by“a“running“program.‘¹fBy“installing“theŽ¡ZODB,–€ you“add“such“fægacilities“to“Python.Ž¦It'‘ÿs8s–]ãcertainly“possible“to“bÌÐuild“your“oÀ wn“system“for“making“Python“objects“persistent.‘9The“usual“starting“points“are“theŽ¡Âpickle–|¹module,›}2for“con™ŸvÙ erting“objects“into“a“string“representation,˜and“vÀ arious“database“modules,˜such“as“the“ÂgdbmŽ¡¹or–}¼Âbsddb“¹modules,‘~0that“proÙ vide“wšægays“to“write“strings“to“disk“and“read“them“back.‘×It'‘ÿs8s“straightforw˜ard“to“combine“theŽ¡Âpickle–0±¹module“and“a“database“module“to“store“and“retrieÀ vÙ e“objects,›\Ýand“in“fægact“the“Âshelve“¹module,˜included“inŽ¡Python'‘ÿs8s–€ standard“library‘ÿY ,“does“this.Ž¦The–_YdoÀ wnside“is“that“t!
he“programmer“has“to“eÙ xplicitly“manage“objects,‘eáreading“an“object“when“it'‘ÿs8s“needed“and“writingŽ¡it–q6out“to“disk“when“the“object“is“no“longer“required.‘ªThe“ZODB‘q2manages“objects“for“you,‘t+kægeeping“them“in“a“cache“andŽ¡writing–€ them“out“if“thešÙ y“haÌÐv˜en'Ñðt“been“accessed“in“a“while.ŽŸ"PÃÄ1.2Ž‘®OODBs–UüvsÑó.‘ µRelational“DBsŽŸpŹAnother–nwægay“to“look“at“it“is“that“the“ZODB‘nis“a“Python-specic“object-oriented“database“(OODB).“Commercial“objectŽ¡databases–for“C++“or“JaÌÐvÀ a“often“require“that“you“jump“through“some“hoops,‘1‡such“as“using“a“special“preprocessor“orŽŽŸ  Ÿô  ‰  ffÕÁHŸ  ó»ló 
   
   phvr7tÅ2ŽŽŽ’‘ò#1‘
  IntroductionŽŽŽŽŽŽŽŽŒ‹                                         9ã ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ì¹a•ÌÐv“oiding–ãªcertain“data“types.‘D—As“we'ægll“see,›ü•the“ZODB‘ã‘has“some“hoops“of“its“oÀ wn“to“jump“through,˜bÌÐut“in“comparisonŽ¤  the–€ naturalness“of“the“ZODB“is“astonishing.Ž©°ÇRelational–×Èdatabases“(RDBs)“are“fægar“more“common“than“OODBs.‘ ñRelational“databases“store“information“in“tables;‘¬aŽ¡table–¯Þconsists“of“anÙ y“number“of“rošÀ ws,‘»Öeach“ro˜w“containing“se˜vÙ eral“columns“of“information.‘©3(Ro˜ws“are“more“formallyŽ¡called–€ relations,“which“is“where“the“term“\relational“database"“originates.)Ž¦Let'‘ÿs8s–Æõlook“at“a“concrete“ešÙ xample.‘îvThe“e˜xample“comes“from“my“day“job“wægorking“for“the“MEMS‘Æ Exchange,‘±in“aŽ¡greatly–/úsimplied“vÙ ersion.‘)†The“job“is“to“track“process“runs,‘[øwhich“are“lists“of“manufægacturing“steps“to“be“performedŽ¡in–î‚a“semiconductor“fægabš™Ÿ.‘eA‘îfrun“is“oÀ wned“by“a“particular“user˜,‘
#and“has“a“name“and“assigned“ID‘îfnumber‘ÿs8.‘eRuns“consistŽ¡of–Ç4a“number“of“operations;‘êÎan“operation“is“a“single“step“to“be“performed,‘Ùsuch“as“depositing“something“on“a“wægafer“orŽ¡etching–€ something“o“it.Ž¦Operations–§Xmay“haÌÐvšÙ e“parameters,‘±.which“are“additional“information“required“to“perform“an“operation.‘ŸF˜or“e˜xample,‘±.ifŽ¡you'‘ÿÿre–ç!depositing“something“on“a“wšægafer™Ÿ,‘ éyou“need“to“knoÀ w“tw˜o“things:‘çÚ1)“what“you'‘ÿÿre“depositing,‘ éand“2)“hoÀ w“muchŽ¡should–€ be“deposited.‘˜Y‘þægou“might“deposit“100“microns“of“silicon“oxide,“or“1“micron“of“copper‘ÿs8.Ž¦Mapping–€ these“structures“to“a“relational“database“is“straightforwægard:Ž¦¤  ‘sçó0ˆÛ 	   
   pcrr7tÇCREATE–ffTABLE“runs“(Ž¡‘'@³int‘ fdrun_id,Ž¡‘'@³varchar‘
ÌÌowner,Ž¡‘'@³varchar‘
ÌÌtitle,Ž¡‘'@³int‘ fdacct_num,Ž¡‘'@³primary‘ffkey(run_id)Ž¡‘sç);Ž¡¡‘sçCREATE–ffTABLE“operations“(Ž¡‘'@³int‘ fdrun_id,Ž¡‘'@³int‘ fdstep_num,Ž¡‘'@³varchar‘
ÌÌprocess_id,Ž¡‘'@³PRIMARY–ffKEY(run_id,“step_num),Ž¡‘'@³FOREIGN–ffKEY(run_id)“REFERENCES“runs(run_id),Ž¡‘sç);Ž¡¡‘sçCREATE–ffTABLE“parameters“(Ž¡‘'@³int‘ fdrun_id,Ž¡‘'@³int‘ fdstep_num,Ž¡‘'@³varchar‘
ÌÌparam_name,Ž¡‘'@³varchar‘
ÌÌparam_value,Ž¡‘'@³PRIMARY–ffKEY(run_id,“step_num,“param_name)Ž¡‘'@³FOREIGN–ffKEY(run_id,“step_num)Ž¡‘7såREFERENCES–ffoperations(run_id,“step_num),Ž¡‘sç);ŽŸ  ŽŽ 5‚«¹In–£äPython,›ìÝyou“wægould“write“three“classes“named“ÂRun¹,˜ÂOperation¹,˜and“ÂParameter¹.‘…EI‘£™wægon'Ñðt“present“code“forŽ¡dening–džthese“classes,‘jsince“that“code“is“uninteresting“at“this“point.‘xEach“class“wægould“contain“a“single“method“to“beÙ ginŽ¡with,›€ an‘˜‰  ff €Ž‘	32‰  ff €Ž–³4Âinit‘ ™˜‰  ff €Ž‘³2‰  ff €Ž“¹method˜that˜assigns˜defægault˜vÀ alues,˜such˜as˜0˜or˜ÂNone¹,˜to˜each˜attribÌÐute˜of˜the˜class.Ž¦It'‘ÿs8s–·˜not“dicult“to“write“Python“code“that“will“create“a“ÂRun“¹instance“and“populate“it“with“the“data“from“the“relationalŽ¡tables;‘´®with–MÊa“little“more“eort,›<you“can“bÌÐuild“a“straightforwægard“tool,˜usually“called“an“object-relational“mapper™Ÿ,˜toŽ¡do–»ºthis“automatically‘ÿY .‘ÌÆ(See“ó»ló 	   
   phvr7tÈhttp://www‘ÿuÃ.amk.ca/pºåython/unmaintained/ordb£Ü.htmlŽ’ Í༹for“a“quick“hack“at“a“Python“object-Ž¡relational–Èðmapper™Ÿ,‘Û,and“Èhttp://www‘ÿuÃ.pºåython.org/wèöor"‰kshops/1997-10/proceedings/shprentz.htmlŽ’ëιfor“Joel“Shprentz'‘ÿs8s“moreŽ¡successful–€ implementation“of“the“same“idea;“Unlikšæge“mine,“Shprentz'‘ÿs8s“system“has“been“used“for“actuual“w˜ork.)Ž¦Ho•À we“vÙ er™Ÿ,‘35it–^is“dicult“to“makšæge“an“object-relational“mapper“reasonably“quick;‘W
a“simple-minded“implementation“lik˜eŽ¡mine–~is“quite“slošÀ w“because“it“has“to“do“se˜vÙ eral“queries“to“access“all“of“an“object'‘ÿs8s“data.‘¤Higher“performance“object-Ž¡relational–€ mappers“cache“objects“to“impro•Ù v“e–€ performance,“only“performing“SQL“queries“when“theÙ y“actually“need“to.ŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å1.2‘
  OODBs–ǧvsÙ .‘p—Relational“DBsŽŽŽ’Ð1ð3ŽŽŽŽŽŽŽŽŒ‹                                         \i ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ì¹That–é$helps“if“you“wšægant“to“access“run“number“123“all“of“a“sudden.‘UBut“what“if“you“w˜ant“to“nd“all“runs“where“a“stepŽ¤  has–€ a“parameter“named“'Ñðthickness'“with“a“vÀ alue“of“2.0?‘˜In“the“relational“všÙ ersion,“you“haÌÐv˜e“twægo“unappealing“choices:Ž©Öz‘€1.ŽŽŽ‘ Write–’Þa“specialized“SQL‘‘Mquery“for“this“case:‘?TÂSELECT–  run‘ ™˜‰  ff €Ž‘šid“FROM“operations“WHEREŽ¡‘ param‘ ™˜‰  ff €Ž›šname–  =“'thickness'“AND“param‘ ™˜‰  ff €Ž˜value“=“2.0ŽŸÔW‘ ¹If–znsuch“queries“are“common,‘¹	you“can“end“up“with“lots“of“specialized“queries.‘áWhen“the“database“tables“getŽ¡‘ rearranged,–€ all“these“queries“will“need“to“be“modied.ŽŸ¨®‘€2.ŽŽŽ‘ An–¤!object-relational“mapper“doesn'Ñðt“help“much.‘…üScanning“through“the“runs“means“that“the“the“mapper“willŽ¡‘ perform–åÁthe“required“SQL‘å§qu!
eries“to“read“run“#1,‘ÿ1and“then“a“simple“Python“loop“can“check“whether“anÙ y“of“itsŽ¡‘ steps–jhaÌÐvÙ e“the“parameter“you'‘ÿÿre“looking“for‘ÿs8.›GRepeat“for“run“#2,–nr3,“and–jso“forth.˜This“does“a“vÀ ast“number“of“SQLŽ¡‘ queries,–€ and“therefore“is“incredibly“sloÀ w‘ÿY .Ž¦An–Îßobject“database“such“as“ZODB‘ÎÊsimply“stores“internal“pointers“from“object“to“object,‘â–so“reading“in“a“single“objectŽ¡is–[much“fægaster“than“doing“a“bÌÐunch“of“SQL‘Záqueries“and“assembling“the“results.‘ªäScanning“all“runs,–‘ßtherefore,“is‘[stillŽ¡inecient,–€ bÌÐut“not“grossly“inecient.Ž©")xÄ1.3Ž‘®What–Uüis“ZEO?ŽŸpŹThe–¢XZODB‘¢
comes“with“a“feÀ w“dierent“classes“that“implement“the“ÂStorage“¹interfægace.‘€ŸSuch“Â*Storage“¹classesŽ¡handle–‡2the“job“of“writing“out“Python“objects“to“a“phó7ysical“storage“medium,‘ˆþwhich“can“be“a“disk“le“(the“ÂFileStor-Ž¡age–Ûí¹class),›òéa“BerkægeleÙ yDB‘ÛÖle“(ÂBerkeleyStorage¹),˜a“relational“database“(ÂDCOrac!
leStorage¹),˜or“some“otherŽ¡medium.‘½ˆZEO‘×adds–ûÂClientStorage¹,‘.ùa“neÀ w“ÂStorage“¹that“doesn'Ñðt“write“to“phó7ysical“media“bÌÐut“just“forwægards“allŽ¡requests–͉across“a“netwægork“to“a“servšÙ er‘ÿs8.‘3The“serv˜er™Ÿ,›àëwhich“is“running“an“instance“of“the“ÂStorageServer“¹class,˜sim-Ž¡ply–4»acts“as“a“front-end“for“some“phó7ysical“ÂStorage“¹class.‘7ÊIt'‘ÿs8s“a“fšægairly“simple“idea,‘aêbÌÐut“as“we'˜ll“see“later“on“in“thisŽ¡document,–€ it“opens“up“manÙ y“possibilities.Ž¦Ä1.4Ž‘®About–Uüthis“guideŽŸpŹThe–Kprimary“author“of“this“guide“wægorks“on“a“project“which“uses“the“ZODB›Jóand“ZEO˜as“its“primary“storage“technology‘ÿY .Ž¡W‘ÿ37e–ÿuse“the“ZODB“to“store“process“runs“and“operations,“a“catalog“of“aÌÐvÀ ailable“processes,“user“information,“accountingŽ¡information,‘B8and–2Æother“data.‘ÿÚPšÙ art“of“the“goal“of“writing“this“document“is“to“makæge“our“e˜xperience“more“widely“aÌÐvÀ ailable.Ž¡A‘ÜHfešÀ w–Ü`times“we'‘ÿÿvÙ e“spent“hours!
“or“e˜vÙ en“days“trying“to“gure“out“a“problem,‘óxand“this“guide“is“an“attempt“to“gó7ather“upŽ¡the–€ knoÀ wledge“we'‘ÿÿvšÙ e“gó7ained“so“that“others“don'Ñðt“haÌÐv˜e“to“makšæge“the“same“mistak˜es“we“did“while“learning.ŽŸ°ÇThis–6sdocument“will“al•ægw“ays–6sbe“a“wægork“in“progress.‘If“you“wish“to“suggest“clarications“or“additional“topics,‘E)please“sendŽ¡your–€ comments“to“Èakuchlin@mems-eºåxchangeÝv.org¹.Ž¦Ä1.5Ž‘®Ac“knoÑówledgementsŽ©pŹI'›ÿÿd–1likæge“to“thank“the“people“who'˜všÙ e“pointed“out“inaccuracies“and“bÌÐugs,‘]boered“suggestions“on“the“te˜xt,‘]bor“proposedŽ¡neÀ w–€ topics“that“should“be“co•Ù v“ered:‘˜Je–€ Bauerš™Ÿ,“W˜illem“Broekægema,“Chris“McDonough,“GeorÑðge“RunÙ yan.ŽŸ'IxÀ2Ž‘hZODB‘ ÈProg•Û$r“ammingŽŸ
_Ä2.1Ž‘®Installing‘UüZODBŽ¦¹The–}ËZODB‘}Êforms“part“of“Zope,‘~<bÌÐut“it'‘ÿs8s“dicult“and“someÀ what“painful“to“eÙ xtract“the“bits“from“Zope“needed“to“supportŽ¡just–æethe“ZODB.“Therefore“I'‘ÿÿvÙ e“assembled“a“distribÌÐution“containing“only“the“packages“required“to“use“the“ZODB‘æKandŽ¡ZEO,–€ so“you“can“install“it“and“start“programming“right“aÙ wægay‘ÿY .ŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å4ŽŽŽ’kÓt2‘
  ZODB‘ǧProg•ægr“ammingŽŽŽŽŽŽŽŽŒ‹                                         l ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ì¹T‘ÿ37o–doÀ wnload“the“distribšÌÐution,‘Cgo“to“my“ZODB‘page“at“Èhttp://www‘ÿuÃ.amk.ca/zÝvodb/Ž‘cÝY¹.‘ The“distrib˜ution“is“still“eÙ xperimental,Ž¤  so–K’don'Ñðt“be“surprised“if“the“installation“process“runs“into“problems.‘Please“inform“me“of“anÙ y“diculties“you“encounter‘ÿs8.ŽŸû`ÅRequirementsŽŸpŹY‘þægou'šægll–˜need“Python,‘ž´of“course;‘¤×vÙ ersion“1.5.2“w˜orks“with“some“xÙ es,‘ž´and“it“also“w˜orks“with“Python“2.0,‘ž´which“is“whatŽ¡I–€ primarily“use.Ž©°ÇThe–©code“is“packaged“using“Distutils,‘sZthe“neÀ w“distribÌÐution“tools“for“Python“introduced“in“Python“2.0.‘”ÙIfŽ¡you'‘ÿÿre–ôusing“1.5.2,‘²rst“you'ægll“haÌÐvÙ e“to“get“the“latest“Distutils“release“from“the“Distutils“SIG‘ójpage“atŽ¡Èhttp://www‘ÿuÃ.pºåython.org/sigs/distutils-sig/doÝvwnload.htmlŽ’ Öý¢¹and–Ýinstall“it.‘0ÞThis“is“simply“a“matter“of“untar!
ring“or“unzippingŽ¡the–€ release“package,“and“then“running“Âpython–  setup.py“install¹.Ž¦If–øzyou'‘ÿÿre“using“1.5.2“and“haÌÐvšÙ e“installed“preÀ vious“v˜ersions“of“the“Distutils,‘V˜be“sure“to“get“the“v˜ery“latest“v˜ersion,Ž¡since–,¦deÀ vÙ eloping“the“ZODB‘,zdistribšÌÐution“turned“up“some“b˜ugs“along“the“wægay‘ÿY .‘‹If“you“encounter“problems“compilingŽ¡`ÈZODB/TimeStamp¯].c¹'–‹Áor“your“compiler“reports“an“error“likæge“\Can'Ñðt“create“bÌÐuild/temp.linux2/ExtensionClass.o:‘1NoŽ¡such–ó¤le“or“directory",‘you“need“an“updated“všÙ ersion.‘t„Old“v˜ersions“of“Distutils“haÌÐv˜e“twægo“bÌÐugs“which“aect“the“setupŽ¡scripts.‘	™First,‘ä for–Ð a“long“time“the“Âdefine‘ ™˜‰  ff €Ž‘šmacros“¹k•ægeÙ yw“ord–Ð in“setup.pšægy“les“didn'Ñðt“w˜ork“due“to“a“Distutils“bÌÐug,‘ä soŽ¡I‘¦¬hackæged–¦¶T¦gimeStamp.c“in“earlier“releases.‘¹The“Distutils“haÌÐvšÙ e“since“been“x˜ed,›°cand“the“hack“became“unnecessary‘ÿY ,˜so“IŽ¡remo•Ù v“ed–ö	it.‘{³Second,!
‘‹the“code“that“creates“directories“tries“to“be“smart“and“caches“them“to“saÌÐvÙ e“time“by“not“trying“toŽ¡create–€ a“directory“twice,“bÌÐut“this“code“wšægas“brok˜en“in“old“vÙ ersions.Ž¦Y‘þægou'ægll–C†need“a“C›Cvcompiler“to“bÌÐuild“the“packages,‘Ožbecause“there“are“vÀ arious“C˜eÙ xtension“modules.‘pAt“the“moment“no“oneŽ¡is–€ making“W™ŸindošÀ ws“binaries“aÌÐv˜ailable,“so“you'ægll“need“a“W™Ÿindo˜ws“de˜vÙ elopment“en™Ÿvironment“to“use“theŽŸû`ÅInstalling–ǧthe“P™ŸacÌÐkagesŽŸpŹDoÀ wnload–lQthe“ZODB›lLtarball“containing“all“the“packages“for“both“ZODB˜and“ZEO˜from“Èhttp://www‘ÿuÃ.amk.ca/les/zÝvodb/Ž‘vI¡¹.Ž¦T‘ÿ37o–‚£bšÌÐuild“the“packages,‘ƒKyou“must“go“into“the“indiÀ vidual“directories“and“b˜uild“and“install“them“one“by“one.‘!€TheÙ y“shouldŽ¡be–€ bÌÐuilt“and“installed“in“this“order:ŽŸÖc‘€1.ŽŽŽ‘ Âzodb-basicŽ¤B>‘€¹2.ŽŽŽ‘ ExtensionClassŽ¡‘€3.ŽŽŽ‘ ZODBŽ¡‘€4.ŽŽŽ‘ ÂBTree–€ ¹and“ÂBTreesŽ¡‘€¹5.ŽŽŽ‘ ZEO!
ŽŸÖdIn–þiparticular™Ÿ,›you“must“install“ExtensionClass“before“bÌÐuilding“the“ZODB‘þHpackage;‘=otherwise,˜the“compilation“in“theŽ¤  ZODB‘¯¥package–¯²will“die“complaining“that“it“can'Ñðt“nd“ExtensionClass.h.‘¨­Y‘þægou“can“manually“hack“the“#include“path“toŽ¡makšæge–€ it“w˜ork“without“installing“ExtensionClass“rst,“bÌÐut“that'‘ÿs8s“a“bit“hackish.Ž¦If–€ you“encounter“anÙ y“problems,“please“let“me“knoÀ w“at“Èakuchlin@mems-eºåxchangeÝv.org¹.ŽŸ!û`Ä2.2Ž‘®HoÑów–UüZODB“W£Üor.ksŽŸpŹThe–Ë«ZODB‘ËVis“conceptually“simple.›üšPython“classes“subclass“a“ÂPersistent“¹class“to“become“ZODB-aÙ wægare.˜In-Ž¡stances–ÑÊof“persistent“objects“are“brought“in“from“a“permanent“storage“medium,›æ<such“as“a“disk“le,˜when“the“programŽ¡needs–°µthem,›¼âand“remain“cached“in“RAM.“The“ZODB‘°¨traps“modications“to“objects,˜so“that“when“a“statement“such“asŽ¡Âobj.size–  =“1–ºG¹is“e•Ù x“ecuted,›ÈØthe–ºGmodied“object“is“markæged“as“\dirty".‘ÈlOn“request,˜anÙ!
 y“dirty“objects“are“written“outŽ¡to–ÆCpermanent“storage;‘édthis“is“called“committing“a“transaction.‘ì`T¦gransactions“can“also“be“aborted“or“rolled“back,‘×ÓwhichŽ¡results–€ in“anšÙ y“changes“being“discarded,“dirty“objects“reÀ v˜erting“to“their“initial“state“before“the“transaction“be˜gó7an.ŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å2.2‘
  HoÙ w–ǧZODB“W³7or&_ksŽŽŽ’Ð1ð5ŽŽŽŽŽŽŽŽŒ‹                                         |C ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ì¹The–Øterm“\transaction"“has“a“specic“technical“meaning“in“computer“science.‘øàIt'‘ÿs8s“eÙ xtremely“important“that“the“contentsŽ¤  of–ma“database“don'Ñðt“get“corrupted“by“softwšægare“or“hardw˜are“crashes,‘"½and“most“database“softw˜are“oers“protection“agó7ainstŽ¡such–äÊcorruption“by“supporting“four“useful“properties,–ýüAtomicity›ÿY ,“ConsistencÙ y˜,“Isolation,“and–äÊDurability˜.‘GöIn“computerŽ¡science–ç0jarÑðgon“these“four“terms“are“collectiÀ všÙ ely“dubbed“the“A™ŸCID‘çproperties,‘ ûforming“an“acron˜ym“from“their“names.Ž¡The–€ denitions“of“the“A™ŸCID“properties“are:ŽŸ°Ç‘ëpÏAtomicityŽŽŽ‘ means–xthat“anÙ y“changes“to“data“made“during“a“transaction“are“all-orÌÐ-nothing.‘AÿEither“all“the“changes“are“applied,Ž¡‘ or–¾none“of“them“are.‘ÒIf“a“program“makšæges“a“bÌÐunch“of“modications“and“then“crashes,‘¿­t!
he“database“w˜on'Ñðt“beŽ¡‘ partially–y¸modied,‘zúpotentially“leaÌÐving“the“data“in“an“inconsistent“state;‘{Ðinstead“all“the“changes“will“be“forÑðgotten.Ž¡‘ That'›ÿs8s–M bad,‘WMbÌÐut“it'˜s“better“than“haÌÐving“a“partially-applied“modication“put“the“database“into“an“inconsistent“state.Ž©  ‘ãB´ConsistencÙ yŽŽŽ‘ means–
lthat“the“data“cannot“be“placed“into“a“logically“in™ŸvÀ alid“state;‘T!sanity“checks“can“be“written“and“enforced.Ž¡‘ Usually–c"this“is“done“by“dening“a“database“schema,‘›êand“requiring“the“data“al•ægw“ays–c"matches“the“schema.‘ÂþFÙ orŽ¡‘ ešÙ xample,‘Òxthis–Áúmight“enforce“that“the“Âorder‘ ™˜‰  ff €Ž‘šnumber“¹attribÌÐute“is“al•ægw“ays–Áúan“inte˜ger™Ÿ,›Òxand“not“a“string,˜tuple,˜orŽ¡‘ other‘€ object.Ž¦‘ñ %IsolationŽŽŽ‘ means–±that“twšægo“programs“or“threads“running“in“tw˜o“dierent“transactions“cannot“see“each“other'‘ÿs8s“changes“untilŽ¡‘ theÙ y–€ commit“their“transactions.Ž¦‘êãþDurabilityŽŽŽ‘!
 means–‘&that“once“a“transaction“has“been“committed,‘Õoa“subsequent“crash“will“not“cause“anÙ y“data“to“be“lost“orŽ¡‘ corrupted.Ž©°ÇThe–z/ZODB›yîproÙ vides“3“of“the“A™ŸCID˜properties.‘%Only“ConsistencÙ y“is“not“supported;‘÷Fthe“ZODB˜has“no“notion“of“aŽ¡database–€ schema,“and“therefore“has“no“wægay“of“enforcing“consistencÙ y“with“a“schema.ŽŸ"PÃÄ2.3Ž‘®Opening–Uüa“ZODBŽŸpŹThere– are“3“main“interfægaces“supplied“by“the“ZODB:“ÂStorage¹,‘.ÂDB¹,“and“ÂConnection“¹classes.‘÷xThe“ÂDB‘†¹and“ÂConnec-Ž¡tion–
¹interfægaces“both“hašÌÐvÙ e“single“implementations,‘%¤b˜ut“there“are“seÀ vÙ eral“dierent“classes“that“implement“the“ÂStorageŽ¡¹interfægace.Ž¦‘ ¸ŽŽŽ‘ ÂStorage–Çü¹classes“are“the“lošÀ west“layer™Ÿ,‘Ùûand“handle“storing“and“retrie˜ving“objects“from“some“form“of“long-termŽ¡‘ storage.‘1ÃA‘ÝKfeÀ w–Ýddierent“types“of“Storage“haÌÐvÙ e“been“written,›ô¼such“as“ÂFileStorage¹,˜which“uses“reÙ gular“diskŽ¡‘ les,–;Øand›*Î!
ÂBerkeleyStorage¹,“which˜uses˜Sleep•ægycat˜Softw“are'‘ÿs8s˜Berk“eleÙ yDB‘*¹database.‘ý2Y‘þægou˜could˜write˜a˜neÀ wŽ¡‘ Storage–1Ñthat“stored“objects“in“a“relational“database“or“Metakit“le,›^Efor“eÙ xample,˜if“that“wægould“better“suit“yourŽ¡‘ application.‘ÉT‘ÿ37wægo–k”eÙ xample“storages,›oªÂDemoStorage“¹and“ÂMappingStorage¹,˜are“aÌÐvÀ ailable“to“use“as“models“ifŽ¡‘ you–€ wægant“to“write“a“neÀ w“Storage.Ž©  ‘ ¸ŽŽŽ‘ ¹The–&ÂDB›%ø¹class“sits“on“top“of“a“storage,‘8and“mediates“the“interaction“between“seÀ vÙ eral“connections.‘ûOne“ÂDB˜¹instanceŽ¡‘ is–€ created“per“process.Ž¦‘ ¸ŽŽŽ‘ ¹Finally‘ÿY ,›the–ï#ÂConnection“¹class“caches“objects,˜and“mo•Ù v“es–ï#them“into“and“out“of“object“storage.‘éOA‘îþmulti-threadedŽ¡‘ program–cÃcan“open“a“separate“ÂConnection“¹instance“for“each“thread.‘.Dierent“threads“can“then“modify“objectsŽ¡‘ and–€ commit“their“modications“independently‘ÿY .ŽŸ°ÇPreparing–cto“use“a“ZODB›!
=requires“3“steps:‘@_you“haÌÐvÙ e“to“open“the“ÂStorage¹,‘8<then“create“a“ÂDB˜¹instance“that“uses“theŽ¡ÂStorage¹,–€ and“then“get“a“ÂConnection“¹from“the“ÂDB‘  instance¹.‘˜All“this“is“only“a“feÀ w“lines“of“code:ŽŸ°Ç‘sçÇfrom–ffZODB“import“FileStorage,“DBŽ¤  ¡‘sçstorage–ff=“FileStorage.FileStorage('/tmp/test-filestorage.fs')Ž¡‘sçdb–ff=“DB(“storage“)Ž¡‘sçconn–ff=“db.open()ŽŸ  ŽŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å6ŽŽŽ’kÓt2‘
  ZODB‘ǧProg•ægr“ammingŽŽŽŽŽŽŽŽŒ‹                                          ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ì¹Note–5}that“you“can“use“a“completely“dierent“data“storage“mechanism“by“changing“the“rst“line“that“opens“a“ÂStorage¹;Ž¤  the›abo•Ù v“e˜e“xample˜uses˜a˜ÂFileStorage¹.‘@ßIn˜section˜3,–^\HoÀ w˜ZEO‘W‘ÿ37orks",“you'ægll˜see˜hoÀ w˜ZEO‘uses˜this˜
eÙ xibilityŽ¡to–€ good“eect.ŽŸ"PÃÄ2.4Ž‘®Wr.iting–Uüa“P‘ÿffersistent“ClassŽŸpŹMaking–¢ia“Python“class“persistent“is“quite“simple;‘³žit“simply“needs“to“subclass“from“the“ÂPersistent“¹class,‘«as“shoÀ wnŽ¡in–€ this“eÙ xample:Ž©°Ç¤  ‘sçÇimport‘ffZODBŽ¡‘sçfrom–ffPersistence“import“PersistentŽ¡¡‘sçclass‘ffUser(Persistent):Ž¡‘2
passŽŸ  ŽŽŸY‚«¹The–(apparently“unnecessary“Âimport‘  ZODB‘ ¹statement“is“needed“for“the“folloÀ wing“Âfrom...import“¹statement“toŽ¡wægork–€ correctly‘ÿY ,“since“the“ZODB“code“does“some“magical“tricks“with“importing.Ž¦FšÙ or–+4simplicity‘ÿY ,‘V!
 in“the“e˜xamples“the“ÂUser“¹class“will“simply“be“used“as“a“holder“for“a“bšÌÐunch“of“attrib˜utes.‘3NormallyŽ¡the–ªÒclass“wægould“dene“vÀ arious“methods“that“add“functionality‘ÿY ,‘µ†bÌÐut“that“has“no“impact“on“the“ZODB'‘ÿs8s“treatment“of“theŽ¡class.Ž¦The–ì‚ZODB‘ìfuses“persistence“by“reachability;‘"Ãstarting“from“a“set“of“root“objects,‘¢all“the“attribÌÐutes“of“those“objects“areŽ¡made–+³persistent,‘VŸwhether“thešÙ y'‘ÿÿre“simple“Python“data“types“or“class“instances.‘±There'‘ÿs8s“no“method“to“e˜xplicitly“storeŽ¡objects–K–in“a“ZODB‘K‰database;‘]simply“assign“them“as“an“attribÌÐute“of“an“object,›Vor“store“them“in“a“mapping,˜that'‘ÿs8s“alreadyŽ¡in–€ the“database.‘˜This“chain“of“containment“must“eÀ vÙ entually“reach“back“to“the“root“object“of“the“database.Ž¦As–‡·an“eÙ xample,‘‰¥we'ægll“create“a“simple“database“of“users“that“allošÀ ws“retrie˜ving“a“ÂUser“¹object“gi˜vÙ en“the“user'‘ÿs8s“ID.“First,Ž¡we–£²retrieÀ vÙ e“the“primary“root“obje!
ct“of“the“ZODB‘£©using“the“Âroot()“¹method“of“the“ÂConnection“¹instance.‘„­The“rootŽ¡object–ÔbehaÌÐvÙ es“likšæge“a“Python“dictionary‘ÿY ,‘+	so“you“can“just“add“a“neÀ w“k˜eÙ y/vÀ alue“pair“for“your“application'‘ÿs8s“root“object.Ž¡W‘ÿ37e'ægll–þinsert“a“ÂBTree“¹object“that“will“contain“all“the“ÂUser“¹objects.‘“¦(The“ÂBTree“¹module“is“also“included“as“part“ofŽ¡Zope.)Ž¦¤  ‘sçÇdbroot–ff=“conn.root()Ž¡¡‘sç#–ffEnsure“that“a“'userdb'“key“is“presentŽ¡‘sç#–ffin“the“rootŽ¡‘sçif–ffnot“dbroot.has_key('userdb'):Ž¡‘2
import‘ffBTreeŽ¡‘2
dbroot['userdb']–ff=“BTree.BTree()Ž¡¡‘sçuserdb–ff=“dbroot['userdb']ŽŸ  ŽŽ  …‚«¹Inserting–‹Ba“neÀ w“user“is“simple:‘0create“the“ÂUser“¹object,›Žll“it“with“data,˜insert“it“into“the“ÂBTree“¹instance,˜and“commitŽ¡this‘€ transaction.ŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å2.4‘
  Wr&_iting–ǧa“P‘ÿÿersistent“ClassŽŽŽ’Ð1ð7ŽŽŽŽŽŽŽŽŒ‹                                         ž< ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ì‘sçÇ#–ffCreate“new“User“instanceŽ¤  ‘sçnewuser–ff=“User()Ž¡¡‘sç#–ffAdd“whatever“attributes“you“want“to“trackŽ¡‘sçnewuser.id–ff=“'amk'Ž¡‘sçnewuser.first_name–ff=“'Andrew'“;“newuser.last_name“=“'Kuchling'Ž¡‘sç...Ž¡¡‘sç#–ffAdd“object“to“the“BTree,“keyed“on“the“IDŽ¡‘sçuserdb[–ffnewuser.id“]“=“newuserŽ¡¡‘sç#–ffCommit“the“changeŽ¡‘sçget_transaction().commit()ŽŸ  ŽŽ  ¦‚«¹When–ˆ™you“import“the“ZODB‘ˆUpackage,›Ê¿it“adds“a“neÀ w“function,˜Âget‘ ™˜‰  ff €Ž‘štransaction()¹,˜to“Python'‘ÿs8s“collection“ofŽ¤  bÌÐuilt-in–Ìyfunctions.‘ÿÂget‘ ™˜‰  ff €Ž‘štransaction()“¹returns“a“ÂTransaction“¹object,‘—which“has“twægo“important“methods:Ž¡Âcommit()– —¹and“Âabort()¹.‘›^Âcommit()“¹writes“anÙ y“modied“objects“to“disk,› ½making“the“changes“permanent,˜whileŽ¡Âabort()–þª¹rolls“back“anšÙ y“changes“that“!
haÌÐv˜e“been“made,‘Trestoring“the“original“state“of“the“objects.‘•—If“you'‘ÿÿre“fægamiliarŽ¡with–€ database“transactional“semantics,“this“is“all“what“you'‘ÿÿd“eÙ xpect.Ž©°ÇBecause–îÝthe“inteÙ gration“with“Python“is“so“complete,‘
”it'›ÿs8s“a“lot“likæge“haÌÐving“transactional“semantics“for“your“program'˜sŽ¡vÀ ariables,–€ and“you“can“eÙ xperiment“with“transactions“at“the“Python“interpreter'‘ÿs8s“prompt:Ž¦‘sçÇ>>>‘ffnewuserŽ¤  ‘sç&lt;User–ffinstance“at“81b1f40>Ž¡‘sç>>>–ffnewuser.first_name‘;fb#“Print“initial“valueŽ¡‘sç'Andrew'Ž¡‘sç>>>–ffnewuser.first_name“=“'Bob'‘32#“Change“first“nameŽ¡‘sç>>>–ffnewuser.first_name‘;fb#“Verify“the“changeŽ¡‘sç'Bob'Ž¡‘sç>>>–ffget_transaction().abort()‘™˜#“Abort“transactionŽ¡‘sç>>>–ffnewuser.first_name‘;fb#“The“value“has“changed“backŽ¡‘sç'Andrew'ŽŸ  ŽŽ  –(ÓÄ2.5Ž‘®Rules–Uüf£Üor“Wr.iting“P‘ÿffersistent“ClassesŽŸpŹPractically–
£all“persistent“languages“impose“some“restrictions“on“programming“style,‘1wægarning“agó7ainst“constructs“theÙ yŽ¡can'Ñðt–˜­handle“or“adding“subtle“semantic“changes,›žØand“the“ZODB‘˜¦is“no“eÙ xception.‘cžHappily‘ÿY ,˜the“ZODB'‘ÿs8s“restrictions“areŽ¡fšægairly–€ simple“to“understand,“and“in“pra!
ctice“it“isn'Ñðt“too“painful“to“w˜ork“around“them.Ž¦The–€ summary“of“rules“is“as“folloÀ ws:ŽŸ°Ç‘ ¸ŽŽŽ‘ ¹If–	Qyou“modify“a“mutable“object“that'›ÿs8s“the“vÀ alue“of“an“object'˜s“attribÌÐute,›+¥the“ZODB‘	.can'Ñðt“catch“that,˜and“wægon'ÑðtŽ¡‘ mark–„šthe“object“as“dirty‘ÿY .‘'eThe“solution“is“to“either“set“the“dirty“bit“yourself“when“you“modify“mutable“objects,‘…ÀorŽ¡‘ use–ÉPa“wrapper“for“Python'‘ÿs8s“lists“and“dictionaries“(ÂPersistentList¹,‘Û¤ÂPersistentMapping¹)“that“will“setŽ¡‘ the–€ dirty“bit“properly‘ÿY .Ž©  ‘ ¸ŽŽŽ‘ ¹Certain– Éof“Python'‘ÿs8s“special“methods“don'Ñðt“wægork“when“theÙ y'‘ÿÿre“dened“on“ExtensionClasses.‘ûòThe“most“imporÌÐ-Ž¡‘ tant–aAones“are“the‘úÙ‰  ff €Ž‘
s‰  ff €Ž–”uÂcmp‘ ™˜‰  ff €Ž‘³2‰  ff €Ž“¹method,‘™‘and–aAthe“reÀ všÙ ersed“v˜ersions“of“binary“arithmetic“operations:‘u²‰  ff €Ž‘L‰  ff €Ž‘NÂradd‘ ™˜‰  ff €Ž‘³2‰  ff €Ž‘34¹,Ž¡‘ ‰  ff €Ž‘œ‰  ff €Ž‘$™žÂrsub‘ ™˜‰  ff €Ž‘³2‰  ff €Ž‘34¹,–€ and“so“forth.Ž¦‘ ¸ŽŽŽ‘ ¹Python'‘ÿs8s–µ/bÌÐuilt-in“Âisinstance()“¹and“Âissubclass()“¹functions“don'Ñðt“wægork“properly“on“ExtensionClasses.Ž¡‘ Solution:‘Ýuse–custom“Âisinstance()“¹and“Âissubclass()“¹functions“that“handle“ExtensionClasses“correctly‘ÿY .Ž¦‘ ¸ŽŽŽ‘ ¹Recent–4zvÙ ersions“of“the“ZODB‘4falloÀ w“writing“a“class“with‘Ή  ff €Ž‘笉  ff €Ž–g®Âsetattr‘ ™˜‰  ff €Ž‘³2‰  ff €Ž“¹,‘Ý,‰  ff €Ž‘öƉ  ff €Ž‘vÈÂgetattr‘ ™˜‰  ff €Ž‘³2‰  ff €Ž‘34¹,‘C”or‘Ή  ff €Ž‘笉  ff €Ž“Âdelattr‘ ™˜‰  ff €Ž‘³2‰  ff €ŽŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å8ŽŽŽ’kÓt2‘
  ZODB‘ǧProg•ægr“ammingŽŽŽŽŽŽŽŽŒ‹   	                                      ©Y ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ì‘ ¹methods.›)Ð(Older–0vÙ ersions“didn'Ñðt“support“this“at“all.)˜If“you“write“such“a‘É«‰  ff €Ž‘	ãE‰  ff €Ž–cGÂsetattr‘ ™˜‰  ff €Ž‘³2‰  ff €Ž“¹or‘É«‰  ff €Ž‘	ãE‰  ff €Ž“Âdelattr‘ ™˜‰  ff €Ž‘³2‰  ff €ŽŽ¤  ‘ ¹method,–€ its“code“has“to“set“the“dirty“bit“manually‘ÿY ,ŽŸ°ÇLet'‘ÿs8s–€ look“at“each“of“these“rules“in“detail.ŽŸ PÃÅModifying–ǧMutabÌÐle“ObjectsŽŸpŹThe–õEZODB‘õ"uses“vÀ arious“Python“hooks“to“catch“attribÌÐute“accesses,‘and“can“trap“most“of“the“wægays“of“modifying“an“object,Ž¡bšÌÐut–eŸnot“all“of“them.‘ÍIf“you“modify“a“ÂUser“¹object“by“assigning“to“one“of“its“attrib˜utes,‘jæas“in“Âuserobj.first‘ ™˜‰  ff €Ž‘šnameŽ¡=‘  'Andrew'¹,›Ö{the–‘üZODB‘‘¶will“mark“the“object“as“haÌÐving“been“changed,˜and“it'ægll“be“written“out“on“the“folloÀ wingŽ¡Âcommit()¹.Ž©°ÇThe–ª{most“common“idiom“that“óK!
jà 
   
   ptmri7t¼isn'³7t“¹caught“by“the“ZODB‘ª.is“mutating“a“list“or“dictionary‘ÿY .‘™If“ÂUser“¹objects“haÌÐvÙ e“aŽ¡attribÌÐute–*named“Âfriends“¹containing“a“list,‘;Jcalling“Âuserobj.friends.append(–  otherUser“)–*¹doesn'Ñðt“markŽ¡Âuserobj–Á¹as“modied;‘h"from“the“ZODB'‘ÿs8s“point“of“vieÀ w‘ÿY ,›AqÂuserobj.friends“¹wægas“only“read,˜and“its“vÀ alue,˜whichŽ¡happened–+ to“be“an“ordinary“Python“list,‘<wšægas“returned.‘ýNThe“ZODB‘+isn'Ñðt“aÙ w˜are“that“the“object“returned“w˜as“subsequentlyŽ¡modied.Ž¦This–•Iis“one“of“the“feÀ w“quirks“you'ægll“hašÌÐvÙ e“to“remember“when“using“the“ZODB;“if“you“modify“a“mutable“attrib˜ute“of“anŽ¡object–Éyin“place,‘Û×you“hašÌÐvÙ e“to“manually“mark“the“object“as“ha˜ving“been“modied“by“setting“its“dirty“bit“to“true.‘öThis“isŽ¡done–€ by“setting“the‘˜‰  ff €Ž‘™šÂp‘ ™˜‰  ff €Ž‘šchanged“¹attribÌÐute“of“the“object“to“true:Ž¦¤  ‘sçÇuserobj.friends.append(–ffotherUser“)Ž¡‘sçuserobj._p_changed–ff=“!
1ŽŸ  ŽŽŸ8‚«¹An–ÐÎobsolete“wægay“of“doing“this“that'‘ÿs8s“still“supported“is“calling“the‘jf‰  ff €Ž‘
„ ‰  ff €Ž‘Âchanged‘ ™˜‰  ff €Ž‘³2‰  ff €Ž‘34()“¹method“instead,‘%bÌÐut“settingŽ¡‰  ff €Ž‘€Âp‘ ™˜‰  ff €Ž‘šchanged–€ ¹is“the“preferred“wægay‘ÿY .Ž¦Y‘þægou–#Ucan“hide“the“implementation“detail“of“haÌÐving“to“mark“objects“as“dirty“by“designing“your“class'‘ÿs8s“API‘#+to“not“useŽ¡direct–Ø-attribšÌÐute“access;‘Dinstead,‘î9you“can“use“the“Ja˜všÀ a-style“approach“of“accessor“methods“for“e˜vÙ erything,‘î9and“then“setŽ¡the–8údirty“bit“within“the“accessor“method.‘ìFšÙ or“e˜xample,›G.you“might“forbid“accessing“the“Âfriends“¹attribÌÐute“directly‘ÿY ,˜andŽ¡add–<Ja“Âget‘ ™˜‰  ff €Ž–šfriend‘ ™˜‰  ff €Ž“list()–<J¹accessor“and“an“Âadd‘ ™˜‰  ff €Ž›šfriend()“¹modier“method“to“the“class.‘NwÂadd‘ ™˜‰  ff €Ž˜friend()Ž¡¹wšægould–€ then“look“lik˜e“this:Ž¦¤  ‘2
Çdef–ffadd_friend(self,“friend):Ž¡‘G§self.friends.append(–ffotherUser“)Ž¡‘G§self._p_changed–ff=“1ŽŸ  ŽŽŸC‚«¹AlternatiÀ všÙ ely‘ÿY ,‘Œ	you–‰¡could“use“a“ZODB!
-a˜wægare“list“or“mapping“type“that“handles“the“dirty“bit“for“you.‘6zThe“ZODB‘‰žcomesŽ¡with–œ‡a“ÂPersistentMapping“¹class,‘£¨and“I'‘ÿÿvÙ e“contribÌÐuted“a“ÂPersistentList“¹class“that'‘ÿs8s“included“in“my“ZODBŽ¡distribÌÐution,–€ and“may“makæge“it“into“a“future“upstream“release“of“Zope.ŽŸ PÃÅSome–ǧSpecial“Methods“Don't“W³7or&_kŽŸpŹDon'šÑðt–Ô³bother“dening“certain“special“methods“on“ExtensionClasses,‘)ßbecause“theÙ y“wægon'˜t“wægork.‘²Most“notably‘ÿY ,‘)ßtheŽ¡‰  ff €Ž‘š‰  ff €Ž‘™œÂcmp‘ ™˜‰  ff €Ž‘³2‰  ff €Ž‘ï¹method–»ßon“an“ExtensionClass“will“nešÀ vÙ er“be“called.‘Í4Neither“will“the“re˜všÙ ersed“v˜ersions“of“binary“arithmeticŽ¡operations,–€ such“as‘˜‰  ff €Ž‘	32‰  ff €Ž–³4Âradd‘ ™˜‰  ff €Ž‘³2‰  ff €Ž“¹and‘˜‰  ff €Ž‘	32‰  ff €Ž“Ârsub‘ ™˜‰  ff €Ž‘³2‰  ff €Ž‘34¹.Ž¦This–äÌis“a“moderately“annoægying“limitation.‘GýIt“means“that“the“ÂPersistentList“¹class“can'Ñðt“implement“comparisonsŽ¡with–À!
rešÙ gular“sequence“objects,‘"and“therefore“statements“such“as“Âif‘  perslist==[]“¹don'Ñðt“do“what“you“e˜xpect;Ž¡instead–ŒJof“performing“the“correct“comparison,‘]thešÙ y“return“some“arbitrary“x˜ed“result,‘]so“the“Âif“¹statement“will“al•ægw“aysŽ¡be–±Xtrue“or“al•ægw“ays–±Xbe“fægalse.‘­¡There“is“no“good“solution“to“this“problem“at“the“moment,‘½®so“all“you“can“do“is“design“classŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å2.5‘
  Rules–ǧf³7or“Wr&_iting“P‘ÿÿersistent“ClassesŽŽŽ’Ð1ð9ŽŽŽŽŽŽŽŽŒ‹   
                                      ¸o ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ì¹interfægaces–€ that“don'Ñðt“need“to“o•Ù v“erload‘˜‰  ff €Ž‘	32‰  ff €Ž–³4Âcmp‘ ™˜‰  ff €Ž‘³2‰  ff €Ž“¹or‘€ the‘˜‰  ff €Ž‘	32‰  ff €Ž“Âr*‘ ™˜‰  ff €Ž‘³2‰  ff €Ž“¹methods.Ž©°ÇThis–]limitation“is“mostly“Python'‘ÿs8s“fægault.‘°ïAs“of“Python“2.1,›”dthe“most“recent“vÙ ersion“at“this“writing,˜the“code“whichŽ¤  handles–Àècomparing“twægo“Python“objects“contains“a“hard-wired“check“for“objects“that“are“class“instances,‘Ñ"which“meansŽ¡that›€ Âtype(obj)–  ==“types.InstanceType¹.‘˜The˜code˜inside˜the˜Python˜interpreter˜looks˜likæge˜this:Ž¦‘sçÇ/*–ffCode“to“compare“objects“v“and“w“*/Ž¤  ‘sçif–ff(PyInstance_Check(v)“||“PyInstance_Check(w))Ž¡‘G§return–ffPyInstance_DoBinOp(v,“w,“"__cmp__",“"__rcmp__",“do_cmp);Ž¡‘sç/*–ffDo“usual“Python“comparison“of“v,w“*/Ž¡‘sçc–ff=“PyObject_Compare(v,“w);ŽŸ  ŽŽŸN‚«¹While–ïOExtensionClasses“try“to“behaÌÐvšÙ e“as!
“much“likæge“re˜gular“Python“instances“as“possible,‘#the˜y“are“still“not“instances,Ž¡and–¯Âtype()“¹doesn'Ñðt“return“the“ÂInstanceType“¹object,‘úáso“no“attempt“is“eÀ vÙ er“made“to“call‘H³‰  ff €Ž‘
bM‰  ff €Ž‘âOÂcmp‘ ™˜‰  ff €Ž‘³2‰  ff €Ž‘34¹.‘¦èPerhapsŽ¡Python–€ 2.2“will“repair“this.ŽŸ PÃÅFixing–ǧÂisinstance“Åand“ÂissubclassŽŸpŹPython'‘ÿs8s–øbÌÐuilt-in“functions“Âisinstance()“¹and“Âissubclass“¹don'Ñðt“wægork“on“ExtensionClass“instances,‘'for“muchŽ¡the–Ósame“reason“that‘«k‰  ff €Ž‘	ʼn  ff €Ž–EÂcmp‘ ™˜‰  ff €Ž‘³2‰  ff €Ž“¹is–ÓneÀ vÙ er“called;‘Z¼in“some“bits“of“the“Python“core“code,‘6Gbranches“are“takægen“only“if“anŽ¡object–¬Íis“of“the“ÂInstanceType“¹type,‘¸ and“this“can“neÀ vÙ er“be“true“for“an“ExtensionClass“instance.‘  Python“2.1“tried“toŽ¡x–¨ˆthis,‘²ªand“changed“these“functions“slightly“in“an“eort“to“makšæge“them“w˜ork“for“ExtensionClasses;‘¼Ìunfortunately‘ÿY ,‘²ªtheŽ¡changes–€ didn'Ñðt“wægork.Ž¦The–ÐEsolution“is“to“use“customized“vÙ ersions“of“these“functions“that“handle“ExtensionClasses“specially“and“fægall“back“toŽ¡the–€ bÌÐuilt-in“všÙ ersion“otherwise.‘˜Here“are“the“v˜ersions“we'‘ÿÿ!
v˜e“written“at“the“MEMS“Exchange:ŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å10ŽŽŽ’kÓt2‘
  ZODB‘ǧProg•ægr“ammingŽŽŽŽŽŽŽŽŒ‹                                         Ê. ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ì¤  ‘sçÇ#–ffThe“built-in“'isinstance()'“and“'issubclass()'“won't“work“onŽ¡‘sç#–ffExtensionClasses,“so“you“have“to“use“the“versions“supplied“here.Ž¡‘sç#–ff(But“those“versions“work“fine“on“regular“instances“and“classes“too,Ž¡‘sç#–ffso“you“should“*always*“use“them.)Ž¡¡‘sçdef–ffissubclass“(class1,“class2):Ž¡‘2
"""A–ffversion“of“'issubclass'“that“works“with“extension“classesŽ¡‘2
as–ffwell“as“regular“Python“classes.Ž¡‘2
"""Ž¡¡‘2
#–ffBoth“class“objects“are“regular“Python“classes,“so“use“theŽ¡‘2
#–ffbuilt-in“'issubclass()'.Ž¡‘2
if–fftype(class1)“is“ClassType“and“type(class2)“is“ClassType:Ž¡‘G§return–ff__builtin__.issubclass(class1,“class2)Ž¡¡‘2
#–ffBoth“so-called“class“objects“have“a“'__bases__'“attribute:“ie.,Ž¡‘2
#–ffthey“aren't“regular“Python“classes,“but“they“sure“look“like“them.Ž¡‘2
#–ffAssume“they“are“extension“classes“a!
nd“reimplement“what“the“builtinŽ¡‘2
#–ff'issubclass()'“does“behind“the“scenes.Ž¡‘2
elif–ffhasattr(class1,“'__bases__')“and“hasattr(class2,“'__bases__'):Ž¡‘G§#–ffXXX“it“appears“that“"ec.__class__“is“type(ec)"“for“anŽ¡‘G§#–ffextension“class“'ec':“could“we/should“we“use“this“as“anŽ¡‘G§#–ffadditional“check“for“extension“classes?Ž¡¡‘G§#–ffBreadth-first“traversal“of“class1's“superclass“tree.‘
ÌÌOrderŽ¡‘G§#–ffdoesn't“matter“because“we're“just“looking“for“a“"yes/no"Ž¡‘G§#–ffanswer“from“the“tree;“if“we“were“trying“to“resolve“a“name,Ž¡‘G§#–fforder“would“be“important!Ž¡‘G§stack–ff=“[class1]Ž¡‘G§while‘ffstack:Ž¡‘]@¯if–ffstack[0]“is“class2:Ž¡‘rÚGreturn‘ff1Ž¡‘]@¯stack.extend(list(stack[0].__bases__))Ž¡‘]@¯del‘ffstack[0]Ž¡‘G§else:Ž¡‘]@¯return‘ff0Ž¡¡‘2
#–ffNot“a“regular“class,“not“an“extension“class:“blow“up“for“consistencyŽ¡‘2
#–ffwith“builtin“'issubclass()"Ž¡‘2
else:Ž¡‘G§raise–ffTypeError,“"arguments“must“be“class“or“ExtensionClass“objects"Ž¡¡‘sç#–ffissubclass“()Ž¡¡‘sçdef–ffisinstance“(object,“klass):Ž¡‘2
"""A–ffversion“of“'isinstance'“that“works“with“extension“classesŽ¡‘2
as–ffwell“as“regular“Python“classes."""Ž¡¡‘2
if–fftype(klass)“is“TypeType:Ž¡‘G§return–ff__builtin__.isinstance(object,“klass)Ž¡‘2
elif–ffhasattr(object,“'__class__'):Ž¡‘G§return–ffissubclass(object.__class__,“klass)Ž¡‘2
else:Ž¡‘G§return‘!
ff0ŽŸ  ŽŽ t‚«¹I'‘ÿÿd–$Ærecommend“putting“these“functions“in“a“module“that“al•ægw“ays–$Ægets“imported.‘éThe“con™ŸvÙ ention“on“my“wægork“projectŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å2.5‘
  Rules–ǧf³7or“Wr&_iting“P‘ÿÿersistent“ClassesŽŽŽ’Ê¢˜11ŽŽŽŽŽŽŽŽŒ‹                                         Ó2 ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ì¹is–]to“put“them“in“`Èmems/lib/baseÝv.pºåy¹',›?´which“contains“vÀ arious“fundamental“classes“and“functions“for“our“system,˜andŽ¤  access–€ them“likæge“this:ŽŸ°Ç¤  ‘sçÇfrom–ffmems.lib“import“baseŽ¡‘sç...Ž¡‘sçif–ffbase.isinstance(object,“Class):“...ŽŸ  ŽŽŸC‚«¹Don'Ñðt–úOinsert“the“modied“functions“into“Python'‘ÿs8s‘“ç‰  ff €Ž‘	­‰  ff €Ž–-ƒÂbuiltin‘ ™˜‰  ff €Ž‘³2‰  ff €Ž“¹module,‘âor–úOimport“just“the“Âisinstance()“¹andŽ¡Âissubclass–—¢¹functions.‘`~If“you“consistently“use“Âbase.isinstance()¹,‘‹then“forÑðgetting“to“import“the“Âbase“¹mod-Ž¡ule–Ÿ2will“result“in“a“ÂNameError“¹eÙ xception.‘w/In“the“case“of“a“forÑðgotten“import,‘¦ÿcalling“the“functions“directly“wægould“useŽ¡Python'‘ÿs8s–€ bšÌÐuilt-in“vÙ ersions,“leading“to“subtle“b˜ugs“that“might“not“be“noticed“for“some“tim!
e.ŽŸ PÑ ™˜‰  ff €Ž‘³2‰  ff €Ž–34Âgetattr‘ ™˜‰  ff €Ž‘³2‰  ff €Ž“Å,‘a?‰  ff €Ž‘	zÙ‰  ff €Ž›úÛÂdelattr‘ ™˜‰  ff €Ž‘³2‰  ff €Ž“Å,‘ǧand‘a?‰  ff €Ž‘	zÙ‰  ff €Ž˜Âsetattr‘ ™˜‰  ff €Ž‘³2‰  ff €ŽŽ©pŹRecent–;:všÙ ersions“of“ZODB‘:DalloÀ w“writing“persistent“classes“that“haÌÐv˜e‘ÔÒ‰  ff €Ž‘îl‰  ff €Ž‘nnÂgetattr‘ ™˜‰  ff €Ž‘³2‰  ff €Ž–34¹,‘߉  ff €Ž‘
Ý9‰  ff €Ž‘];Âdelattr‘ ™˜‰  ff €Ž‘³2‰  ff €Ž“¹,‘*orŽ¡‰  ff €Ž‘š‰  ff €Ž‘™œÂsetattr‘ ™˜‰  ff €Ž‘³2‰  ff €Ž‘Úr¹methods.‘RThe–§>one“minor“complication“is“that“the“machinery“for“automatically“detecting“changesŽ¡to–ïüthe“object“is“disabled“while“the‘‰”‰  ff €Ž‘	£.‰  ff €Ž›#0Âgetattr‘ ™˜‰  ff €Ž‘³2‰  ff €Ž–34¹,‘¥’‰  ff €Ž‘	¿,‰  ff €Ž‘?.Âdelattr‘ ™˜‰  ff €Ž‘³2‰  ff €Ž“¹,‘úor‘‰”‰  ff €Ž‘	£.‰  ff €Ž˜Âsetattr‘ ™˜‰  ff €Ž‘³2‰  ff €Ž˜¹method–ïüis“e•Ù x“ecuting.‘i‹ThisŽ¡means–0that“if“the“ob!
ject“is“modied,‘@
the“object“should“be“markæged“as“dirty“by“setting“the“object'‘ÿs8s‘ɨ‰  ff €Ž‘IªÂp‘ ™˜‰  ff €Ž‘šchanged“¹methodŽ¡to‘€ true.ŽŸ"PÃÄ2.6Ž‘®Wr.iting–UüP‘ÿffersistent“ClassesŽ¦¹NoÀ w–Ýthat“we'‘ÿÿvÙ e“lookšæged“at“the“basics“of“programming“using“the“ZODB,“we'˜ll“turn“to“some“more“subtle“tasks“that“areŽ¡likægely–€ to“come“up“for“anÙ yone“using“the“ZODB“in“a“production“system.ŽŸ PÃÅChanging–ǧInstance“Attr&_ibÌÐutesŽ¦¹Ideally‘ÿY ,‘Zbefore–P˜making“a“class“persistent“you“wšægould“get“its“interf˜ace“right“the“rst“time,‘Zso“that“no“attribÌÐutes“w˜ould“eÀ vÙ erŽ¡need–åTto“be“added,›þ©remo•Ù v“ed,˜or›åThaÌÐv“e˜their˜interpretation˜change˜o“v“er˜time.‘I”It'‘ÿs8s˜a˜wægorthó7y˜goal,‘þ©bÌÐut˜also˜an˜impracticalŽ¡one–¿unless“you'‘ÿÿre“gifted“with“perfect“knoÀ wledge“of“the“future.‘Ö¸Such“unnatural“foresight“can'Ñðt“be“required“of“anÙ yŽ¡person,‘Žäso–‹êyou“therefore“haÌÐvÙ e“to“be“prepared“to“handle“such“stru!
ctural“changes“gracefully‘ÿY .‘=UIn“object-oriented“databaseŽ¡terminology‘ÿY ,›\dthis–S|is“a“schema“update.‘
ÂThe“ZODB‘Sqdoesn'Ñðt“haÌÐvÙ e“an“actual“schema“specication,˜bÌÐut“you'‘ÿÿre“changing“theŽ¡softwægare'‘ÿs8s–€ eÙ xpectations“of“the“data“contained“by“an“object,“so“you'‘ÿÿre“implicitly“changing“the“schema.Ž©°ÇOne–$˜wægay“to“handle“such“a“change“is“to“write“a“one-time“con™ŸvšÙ ersion“program“that“will“loop“o˜v˜er“eÀ v˜ery“single“objectŽ¡in–ý¨the“database“and“update“them“to“match“the“neÀ w“schema.‘’‘This“can“be“easy“if“your“netwægork“of“object“references“isŽ¡quite–›Ãstructured,‘¢´making“it“easy“to“nd“all“the“instances“of“the“class“being“modied.‘lâFšÙ or“e˜xample,‘¢´if“all“ÂUser“¹objectsŽ¡can–ÏÝbe“found“inside“a“single“dictionary“or“B-tree,‘ãÔthen“it“wægould“be“a“simple“matter“to“loop“o•Ù v“er›ÏÝeÀ v“ery˜ÂUser˜¹instanceŽ¡with–ésa“Âfor“¹statement.‘UòThis“is“more“dicult“if“your“object“graph“is“less“structured;‘-if“ÂUser“¹objects“can“be“found“asŽ¡attribÌÐutes–JÌof“anšÙ y“number“of“dierent“class“instances,‘}~then“there'‘ÿs8s“no“longer“!
an˜y“easy“wægay“to“nd“them“all,‘}~short“ofŽ¡writing–DÓa“generalized“object“traÌÐvÙ ersal“function“that“wšægould“w˜alk“o•Ù v“er›DÓeÀ v“ery˜single˜object˜in˜a˜ZODB,˜checking˜each˜oneŽ¡to–]Îsee“if“it'‘ÿs8s“an“instance“of“ÂUser¹.‘2Ÿü^ÿó3{Ù    
   ptmr7tÉ1ŽŽ‘
d¹Some“OODBs“support“a“feature“called“eÙ xtents,‘d¥which“alloÀ w“quickly“nding“all“theŽ¡instances–~Uof“a“giÀ všÙ en“class,‘~ªno“matter“where“the˜y“are“in“the“object“graph;‘~ãunfortunately“the“ZODB‘~Tdoesn'Ñðt“oer“e˜xtentsŽ¡as–€ a“feature.Ž¦XXX–€ Rest“of“section“not“written“yet:‘³0‰  ff €Ž‘	Ìʉ  ff €Ž‘LÌgetstate‘ ™˜‰  ff €Ž‘³2‰  ff €Ž–34/‘ ™˜‰  ff €Ž‘³2‰  ff €Ž“setstate‘ ™˜‰  ff €Ž‘³2‰  ff €ŽŽŸff‰  ff »æ-Ÿ	ƒè‘
æhŸý-:ó&3{Ù    
   ptmr7tÑ1ŽŽŽ‘fhó3{Ù    
   ptmr7tÊXXX‘d«is–dÅthere“a“con®váMenience“method“for“wë†alking“the“object“graph“hiding“someÌÍwhere“inside“DC'`s“code?‘©.Should“there“be“a“utility“method“forŽŸ	€ doing–  this?‘zàShould“I“write“one“and“include“it“in“this“section?ŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å12ŽŽŽ’kÓt2‘
  ZODB‘ǧProg•ægr“ammingŽŽŽŽŽŽŽŽŒ‹   
                                      Ýh ¨n ý>‘ìŸüfdŽŽŽŽ £n ý€.ÌÀ3Ž‘hZEOŽŸ
_Ä3.1Ž‘®HoÑów–UüZEO“W£Üor.ksŽŸpŹThe–~.ZODB,“as“I'‘ÿÿvÙ e“described“it“so“fægar™Ÿ,‘~Œcan“only“be“used“within“a“single“Python“process“(though“perhaps“with“multipleŽ¤  threads).‘Ž ZEO,–ü#Zope“Enterprise“Objects,‘+ešÙ xtends“the“ZODB‘ümachinery“to“pro˜vide“access“to“objects“o˜v˜er“a“netwægork.Ž¡The–name“"Zope“Enterprise“Objects"“is“a“bit“misleading;‘R ZEO‘òcan“be“used“to“store“Python“objects“and“access“themŽ¡in–í0a“distribÌÐuted“fægashion“without“Zope“eÀ vÙ er“entering“the“picture.‘a)The“combination“of“ZEO›íand“ZODB˜is“essentially“aŽ¡Python-specic–€ object“database.Ž©°ÇZEO‘Gconsists–G8of“about“1400“lines“of“Python“code.‘o?The“code“is“relatiÀ vÙ ely“small“because“it“contains“only“code“for“aŽ¡TCP/IP‘0ÊservÙ er™Ÿ,›]5and–0÷for“a“neÀ w“type“of“Storage,˜ÂClientStorage¹.‘,~ÂClientStorage“¹doesn'Ñðt“use“disk“les“at“all;Ž¡it–5dsimply“makæges“remote“procedure“calls“to“the“servšÙ er™Ÿ,‘b¼which“then“passes“them“on“a“re˜gul!
ar“ÂStorage“¹class“such“asŽ¡ÂFileStorage¹.‘˜The–€ folloÀ wing“diagram“lays“out“the“system:Ž¦XXX–€ insert“diagram“here“laterŽ¦AnšÙ y–swnumber“of“processes“can“create“a“ÂClientStorage“¹instance,‘uøand“an˜y“number“of“threads“in“each“process“can“beŽ¡using–Þthat“instance.‘5ÂClientStorage“¹aggressiÀ vÙ ely“caches“objects“locally‘ÿY ,›ö!so“in“order“to“a•ÌÐv“oid–Þusing“stale“data,˜theŽ¡ZEO‘ìÄservÙ er–ìàsends“an“in™ŸvšÀ alidate“message“to“all“the“connected“ÂClientStorage“¹instances“on“e˜vÙ ery“write“operation.Ž¡The–všin™ŸvÀ alidate“message“contains“the“object“ID‘v[for“each“object“that'‘ÿs8s“been“modied,‘´@letting“the“ÂClientStorageŽ¡¹instances–€ delete“the“old“data“for“the“giÀ vÙ en“object“from“their“caches.Ž¦This–²¾design“decision“has“some“consequences“you“should“be“aÙ wægare“of.‘±ÓFirst,›¿nwhile“ZEO‘²±isn'Ñðt“tied“to“Zope,˜it“wægas“rstŽ¡written–jhfor“use“with“Zope,›n¹which“stores“HTML,“images,˜and“program“code“in“the“database.‘eAs!
“a“result,˜reads“from“theŽ¡database–|ýare“¼far“¹more“frequent“than“writes,‘}—and“ZEO‘|üis“therefore“better“suited“for“read-intensišÀ vÙ e“applications.‘—If“e˜vÙ eryŽ¡ÂClientStorage–oð¹is“writing“to“the“database“all“the“time,‘s&this“will“result“in“a“storm“of“in™ŸvÀ alidate“messages“being“sent,Ž¡and–€ this“might“takæge“up“more“processing“time“than“the“actual“database“operations“themselvÙ es.Ž¦On–‘the“other“hand,›Btfor“applications“that“haÌÐvÙ e“feÀ w“writes“in“comparison“to“the“number“of“read“accesses,˜this“aggres-Ž¡sišÀ vÙ e–¿Rcaching“can“be“a“major“win.‘×Consider“a“Slashdot-likæge“discussion“forum“that“di˜vides“the“load“among“se˜vÙ eralŽ¡W‘ÿ37eb–ó1servÙ ers.‘s+If“neÀ ws“items“and“postings“are“represented“by“objects“and“accessed“through“ZEO,“then“the“most“heaÌÐv-Ž¡ily–³¯accessed“objects“{“the“most“recent“or“most“popular“postings“{“will“vÙ ery“quickly“wind“up“in“the“caches“of“theŽ¡ÂClientStorage–B¹instances“on“the“front-end“servšÙ ers!
.‘©]The“back-end“ZEO‘serv˜er“will“do“relatiÀ v˜ely“little“wægork,‘&’onlyŽ¡being–2¾called“upon“to“return“the“occasional“older“posting“that'‘ÿs8s“requested,‘B2and“to“send“the“occasional“in™ŸvÀ alidate“messageŽ¡when–O
a“nešÀ w“posting“is“added.‘	FThe“ZEO‘NýservÙ er“isn'Ñðt“going“to“be“contacted“for“e˜vÙ ery“single“request,‘XÕso“its“wægorkload“willŽ¡remain‘€ manageable.ŽŸ"PÃÄ3.2Ž‘®Installing‘UüZEOŽ¤pŹThis–-‡section“co•Ù v“ers–-‡hošÀ w“to“install“the“ZEO‘-rpackage,‘>and“ho˜w“to“congure“and“run“a“ZEO‘-rStorage“ServÙ er“on“a“machine.ŽŸ PÃÅRequirementsŽ¡¹T‘ÿ37o–-Šrun“a“ZEO‘-uservÙ er™Ÿ,›>you'ægll“need“Python“1.5.2“or“2.0,˜and“the“ZODB‘-upackages“from“Èhttp://www‘ÿuÃ.amk.ca/les/zÝvodb/Ž‘x8d¹haÌÐvÙ eŽ¤  to–€ be“installed.Ž¦¼Note–Dfor“Python“1.5.2“userægs¹:‘û§ZEO‘Drequires“updated“vÙ ersions“of“the“Âasyncore.py“¹and“Âasynchat.py“¹modules“thatŽ¡are–MÎincluded“in“1.5.2'‘ÿs8s“standard“library‘ÿY .‘ƒCurrent“všÙ ersions“of“the“ZODB‘M˜distribÌÐution“install“pri•À v“ate–MÎv˜ersions“of“theseŽ¡modules,‘—so–ÿ¬you“shouldn'Ñðt“need“to“grab“updated“vÙ ersions“yourself.‘˜œ(The“symptom“of“this“problem“is“a“traceback“onŽ¡attempting–zŒto“run“a“ZEO!
‘zŠclient“program:›Þthe“traceback“is“\T‘ÿ37ypeError:˜too“manšÙ y“arÑðguments;‘|]e˜xpected“2,‘{£got“3"“aroundŽ¡line–€ 100“of“`Èsmac.pºåy¹'.ŽŽŸ  Ÿô  ‰  ffÕÁHŸ  ’Ê¢˜Å13ŽŽŽŽŽŽŽŽŒ‹                                         ñ] ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ìÅInstallationŽ©pŹInstalling–õïthe“ZEO‘õÌpackage“is“easy‘ÿY .›ë“Just“run“Âpython–  setup.py“install¹.˜This–õïwill“install“the“ZEO/“package“intoŽ¤  your–“‰Python“installation,‘˜kand“copægy“vÀ arious“les“into“their“proper“locations:‘@©`ÈzÝveo£Ü.conf¹'“will“be“put“into“`È/usr/local/etc/¹',Ž¡a–€ `ÈzšÝveo¹'“startup“script“will“be“put“in“`È/etc/rc.d/init.d/¹',“and“the“`Èz˜eod¹'“daemon“program“will“be“placed“in“`È/usr/local/bin¹'.ŽŸ"PÃÄ3.3Ž‘®Congur.ing–Uüand“Running“a“ZEO“Ser\#v³3erŽ¦¹Edit–ͯÂ/usr/local/etc/zeo.conf“¹appropriately“for“your“desired“setup.‘¤This“conguration“le“controls“the“portŽ¡on–Wwhich“ZEO‘/will“listen“for“connections,‘Cmthe“user“and“group“IDs“under“which!
“the“servšÙ er“will“be“e˜x˜ecuted,‘Cmand“theŽ¡location–€ of“the“concrete“ÂStorage“¹object“that“will“be“made“netwægork-accessible.ŽŸ"PÃÄ3.4Ž‘®T‘þ`esting–Uüthe“ZEO“InstallationŽ¦¹Once–Ãaa“ZEO›ÃPservÙ er“is“up“and“running,‘Ô9using“it“is“just“likæge“using“ZODB˜with“a“more“con™ŸvÙ entional“disk-based“storage;Ž¡no–AneÀ w“programming“details“are“introduced“by“using“a“remote“servÙ er‘ÿs8.‘ŸThe“only“dierence“is“that“programs“must“createŽ¡a–ÿlÂClientStorage“¹instance“instead“of“a“ÂFileStorage“¹instance.‘—ÛFrom“that“point“onwægard,‘FZODB-based“code“isŽ¡happily–€ unašÙ wægare“that“objects“are“being“retrieÀ v˜ed“from“a“ZEO“serv˜er™Ÿ,“and“not“from“the“local“disk.Ž©°ÇAs–s
an“eÙ xample,›¯Ìand“to“test“whether“ZEO‘rËis“wægorking“correctly‘ÿY ,˜try“running“the“folloÀ wing“lines“of“code,˜which“willŽ¡connect–€ to“the“servÙ er™Ÿ,“add“some“bits“of“data“to“the“root“of“the“ZODB,“and“commits“the“transaction:Ž¦¤  ‘sçÇfrom–ffZEO“import“ClientStorageŽ¡‘sçfrom–ffZODB“import“DBŽ¡¡‘sç#–ffChange“next“line“to“connect“to“your“ZEO“serverŽ¡‘sçaddr–ff=“('kronos.example.com',“1975)Ž¡‘sçstorage–ff=“ClientStorage.ClientStorage(addr)Ž¡‘sçdb–ff=“DB(“storage“)Ž¡‘sçconn–ff=“db.open()Ž¡‘sçroot–ff=“conn.root()Ž¡¡‘sç#–ffStore“some“things“in“the“rootŽ¡‘sçroot['list']–ff=“['a',“'b',“1.0,“3]Ž¡‘sçroot['dict']–ff=“{'a':1,“'b':4}Ž¡¡‘sç#–ffCommit“the“transactionŽ¡‘sçget_transaction().commit()ŽŸ  ŽŽ  Ò‚«¹If–€ this“code“runs“properly›ÿY ,“then“your“ZEO“servÙ er“is“wægorking“correctly˜.Ž¤"PÃÄ3.5Ž‘®ZEO›UüProg•áIr“amming˜NotesŽ©pŹXXX–€ The“Connection.sync()“method“and“its“necessity“(if“it“wægorks“at“all!)Ž¡Ä3.6Ž‘®Sampl!
e‘UüApplication:‘ µchatter‘ÿff.p£ÜyŽ¦¹FšÙ or–øWan“e˜xample“application,‘xwe'ægll“bÌÐuild“a“little“chat“application.‘ì`What'›ÿs8s“interesting“is“that“none“of“the“application'˜s“codeŽ¤  deals–ºQwith“netwægork“programming“at“all;‘×yinstead,›Èåan“object“will“hold“chat“messages,˜and“be“magically“shared“betweenŽ¡all–œÇthe“clients“through“ZEO.“I›œ¿wægon'Ñðt“present“the“complete“script“here;‘«*it'‘ÿs8s“included“in“my“ZODB˜distribÌÐution,‘£øand“youŽ¡can–€ doÀ wnload“it“from“Èhttp://www‘ÿuÃ.amk.ca/zÝvodb/demos/Ž’ _¹.‘˜Only“the“interesting“portions“of“the“code“will“be“co•Ù v“ered‘€ here.ŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å14ŽŽŽ’±¢¡3‘
  ZEOŽŽŽŽŽŽŽŽŒ‹                                        × ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ì¹The–¸àbasic“data“structure“is“the“ÂChatSession“¹object,‘which“proÙ vides“an“Âadd‘ ™˜‰  ff €Ž‘šmessage()“¹method“that“adds“aŽ¤  message,‘àÓand–Íva“Ânew‘ ™˜‰  ff €Ž‘šmessages()“¹method“that“returns“a“list“of“neÀ w“messages“that“haÌÐvÙ e“accumulated“since“the“lastŽ¡call–øƒto“Ânew‘ ™˜‰  ff €Ž‘šmessages()¹.‘ƒ Internally›ÿY ,‘£ÂChatSession“¹maintains“a“B-tree“that“uses“the“time“as“the“kægeÙ y˜,‘£and“storesŽ¡the–€ message“as“the“corresponding“vÀ alue.Ž¤°ÇThe–€ constructor“for“ÂChatSession“¹is“pretty“simple;“it“simply“creates“an“attribÌÐute“containing“a“B-tree:Ž¡¤  ‘sçÇclass‘ffChatSession(Persistent):Ž¡‘2
def–ff__init__(self,“name):Ž¡‘G§self.name–ff=“nameŽ¡‘G§#–ffInternal“attribute:“_messages“holds“all“the“chat“messages.Ž¡‘G§self._messages–ff=“BTree.BTree()ŽŸ  ŽŽŸY‚«Âadd‘ ™˜‰  ff €Ž‘šmessage()–u¹has“to“add“a“message“to“t!
he‘¬
‰  ff €Ž‘	,Âmessages“¹B-tree.‘ÐöA‘Ocomplication“is“that“it'‘ÿs8s“possible“that“someŽ¤  other–Cclient“is“trying“to“add“a“message“at“the“same“time;‘˜dwhen“this“happens,›”Sthe“client“that“commits“rst“wins,˜and“theŽ¡second–êclient“will“get“a“ÂConflictError“¹ešÙ xception“when“it“tries“to“commit.‘÷<F˜or“this“application,‘-ˆÂConflictErrorŽ¡¹isn'Ñðt–fÌserious“bÌÐut“simply“means“that“the“operation“has“to“be“retried;‘o2other“applications“might“treat“it“as“a“fægatal“error‘ÿs8.‘1TheŽ¡code–8Öuses“Âtry...except...else“¹inside“a“Âwhile“¹loop,‘Gbreaking“out“of“the“loop“when“the“commit“wægorks“withoutŽ¡raising–€ an“eÙ xception.ŽŸ°Ç¤  ‘2
Çdef–ffadd_message(self,“message):Ž¡‘G§"""Add–ffa“message“to“the“channel.Ž¡‘G§message–ff--“text“of“the“message“to“be“addedŽ¡‘G§"""Ž¡¡‘G§while‘ff1:Ž¡‘]@¯try:Ž¡‘rÚGnow–ff=“time.time()Ž¡‘rÚGself._messages[–ffnow“]“=“messageŽ¡‘rÚGget_transaction().commit()Ž¡‘]@¯except‘ffConflictError:Ž¡‘rÚG#–ffC!
onflict“occurred;“this“process“should“pause“andŽ¡‘rÚG#–ffwait“for“a“little“bit,“then“try“again.Ž¡‘rÚGtime.sleep(.2)Ž¡‘rÚGpassŽ¡‘]@¯else:Ž¡‘rÚG#–ffNo“ConflictError“exception“raised,“so“breakŽ¡‘rÚG#–ffout“of“the“enclosing“while“loop.Ž¡‘rÚGbreakŽ¡‘G§#–ffend“whileŽŸ  ŽŽ  þ‰Ânew‘ ™˜‰  ff €Ž‘šmessages()–uú¹introduces“the“use“of“¼volatile“¹attrib•ÌÐutes.‘AAttrib“utes–uúof“a“persistent“object“that“beÙ gin“with‘’‰  ff €Ž–”Âv‘ ™˜‰  ff €Ž“¹areŽ¡considered–¯IvÌÐolatile“and“are“neÀ vÙ er“stored“in“the“database.‘§rÂnew‘ ™˜‰  ff €Ž‘šmessages()“¹needs“to“store“the“last“time“the“methodŽ¡wšægas–×Hcalled,‘íbÌÐut“if“the“time“w˜as“stored“as“a“reÙ gular“attribÌÐute,‘íits“vÀ alue“w˜ould“be“committed“to“the“database“and“sharedŽ¡with–2[all“the“other“clients.‘ÿ·Ânew‘ ™˜‰  ff €Ž‘šmessages()“¹wægould“then“return“the“neÀ w“messages“accumulated“since“anÙ y“other“clientŽ¡called–€ Ânew‘ ™˜‰  ff €Ž‘šmessages()¹,“which“isn'Ñðt“what“w!
e“wægant.ŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å3.6‘
  Sample‘ǧApplication:‘p—chatter‘ÿÿ.p³7yŽŽŽ’Ê¢˜15ŽŽŽŽŽŽŽŽŒ‹                                        7 ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ì‘2
Çdef‘ffnew_messages(self):Ž¤  ‘G§"Return–ffnew“messages."Ž¡¡‘G§#–ffself._v_last_time“is“the“time“of“the“most“recent“messageŽ¡‘G§#–ffreturned“to“the“user“of“this“class.Ž¡‘G§if–ffnot“hasattr(self,“'_v_last_time'):Ž¡‘]@¯self._v_last_time–ff=“0Ž¡¡‘G§new–ff=“[]Ž¡‘G§T–ff=“self._v_last_timeŽ¡¡‘G§for–ffT2,“message“in“self._messages.items():Ž¡‘]@¯if–ffT2“>“T:Ž¡‘rÚGnew.append(–ffmessage“)Ž¡‘rÚGself._v_last_time–ff=“T2Ž¡¡‘G§return‘ffnewŽŸ  ŽŽ  Ò‚«¹This–hQapplication“is“interesting“because“it“uses“ZEO›hKto“easily“share“a“data“structure;‘p6ZEO˜and“ZODB˜are“being“used“forŽ¤  their–Á`netwægorking“ability›ÿY ,‘Ѹnot“primarily“for“their“data“storage“ability˜.‘ݹI‘ÁPcan“foresee“manÙ y“interesting“applications“usingŽ¡ZEO–€ in“this“wægay:ŽŸ°Ç‘ ¸ŽŽŽ‘ ¹Wš™Ÿith–+±a“Tkinter“front-end,‘<Ž!
and“a“cleÀ vÙ erer˜,›<Žmore“scalable“data“structure,˜you“could“bÌÐuild“a“shared“whiteboard“usingŽ¡‘ the–€ same“technique.Ž¤  ‘ ¸ŽŽŽ‘ ¹A–€ shared“chessboard“object“wšægould“mak˜e“writing“a“netw˜ork˜ed“chess“gó7ame“easy‘ÿY .Ž¡‘ ¸ŽŽŽ‘ ¹Y‘þægou–¦mcould“create“a“Python“class“containing“a“CD'‘ÿs8s“title“and“track“information.‘ŒÞT‘ÿ37o“makæge“a“CD‘¦cdatabase,‘°a“read-Ž¤  ‘ only–ÅZEO›ÄñservÙ er“could“be“opened“to“the“wægorld,‘ÖDor“an“HTTP˜or“XML-RPC˜interfægace“could“be“written“on“top“ofŽ¡‘ the‘€ ZODB.ŽŸ  ‘ ¸ŽŽŽ‘ ¹A‘†ëprogram–†ílikšæge“Quick˜en“could“use“a“ZODB‘†ëon“the“local“disk“to“store“its“data.‘._This“a•ÌÐv“oids–†íthe“need“to“write“andŽ¡‘ maintain–]„specialized“I/O‘]{code“that“reads“in“your“objects“and“writes“them“out;‘iinstead“you“can“concentrate“on“theŽ¡‘ problem–€ domain,“writing“objects“that“represent“cheques,“stock“portfolios,“or“whateÀ vÙ er‘ÿs8.ŽŸ'pÃÀ4Ž‘hT‘þE¦rÛ$ansactions– Èand“V‘þÙersi!
oningŽŸ
_Ä4.1Ž‘®SubtráIansactionsŽŸpŹSubtransactions–¶can“be“created“within“a“transaction.‘»×Each“subtransaction“can“be“indiÀ vidually“committed“and“aborted,Ž¡bÌÐut–€ the“changes“within“a“subtransaction“are“not“truly“committed“until“the“containing“transaction“is“committed.Ž©°ÇThe–primary“purpose“of“subtransactions“is“to“decrease“the“memory“usage“of“transactions“that“touch“a“vÙ ery“larÑðge“numberŽ¡of–ô\objects.›v­Consider“a“transaction“during“which“200,000“objects“are“modied.˜All“the“objects“that“are“modied“in“aŽ¡single–cƒtransaction“haÌÐvÙ e“to“remain“in“memory“until“the“transaction“is“committed,‘i6because“the“ZODB‘c|can'Ñðt“discard“themŽ¡from–Ákthe“object“cache.›ÝØThis“can“potentially“makæge“the“memory“usage“quite“larÑðge.˜W™Ÿith“subtransactions,‘ÑÅa“commit“canŽ¡be–Ebe“performed“at“intervšÀ als,–$say‘ÿY ,“e˜vÙ ery–E10,000“objects.‘£hThose“10,000“objects“are“then“written“to“permanent“storageŽ¡and–€ can“be“purÑðged“from“the“cache“t!
o“free“more“space.Ž¦T‘ÿ37o–¡¢commit“a“subtransaction“instead“of“a“full“transaction,‘ªpass“a“true“vÀ alue“to“the“Âcommit()“¹or“Âabort()“¹method“ofŽ¡the–€ ÂTransaction“¹object.ŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å16ŽŽŽ’I“4‘
  T‘þÌÐrægansactions–ǧand“V‘ÿ37ersioningŽŽŽŽŽŽŽŽŒ‹                                        1 ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ì¤  ‘sçÇ#–ffCommit“a“subtransactionŽ¡‘sçget_transaction().commit(1)Ž¡¡‘sç#–ffAbort“a“subtransactionŽ¡‘sçget_transaction().abort(1)ŽŸ  ŽŽŸY‚«¹A–€ nešÀ w“subtransaction“is“automatically“started“on“committing“or“aborting“the“pre˜vious“subtransaction.ŽŸ"PÃÄ4.2Ž‘®Undoing‘UüChangesŽŸpŹSome–Žtypes“of“ÂStorage“¹support“undoing“a“transaction“eÀ vÙ en“after“it'‘ÿs8s“been“committed.‘CÓY‘þægou“can“tell“if“this“is“the“caseŽ¤  by–ãecalling“the“ÂsupportsUndo()“¹method“of“the“ÂDB‘ãK¹instance,‘ü>which“returns“true“if“the“underlying“storage“supportsŽ¡undo.‘˜AlternatiÀ vÙ ely–€ you“can“call“the“ÂsupportsUndo()“¹method“on“the“underlying“storage“instance.Ž©°ÇIf–Ò0a“database“supports“undo,‘æ¼then“the“ÂundoLog(¼start.Â,–  ¼endEó3{Ù ff 
   ptmr7t¿[Â,“func‘l1¿]Â)–Ò0¹method“on“the“ÂDB‘Ò¹instance“returns“the“logŽ¡of–äÔpast“transactions,›þ	returning“transactions“between“the“times“¼start‘ã¹and“¼endE¹,˜measured“in“seconds“from“the“epoch.‘HIfŽ¡present,‘ù°¼func–áY¹is“a“function“that“acts“as“a“lter“on“the“transactions“to“be“returned;‘it'‘ÿs8s“passed“a“dictionary“representingŽ¡each–ãtransaction,‘)‚and“only“transactions“for“which“¼func“¹returns“true“will“be“included“in“the“list“of“transactions“returned“toŽ¡the–¨caller“of“ÂundoLog()¹.›‘The“dictionary“contains“kægeÙ ys“for“vÀ arious“properties“of“the“transaction.˜The“most“importantŽ¡kšægeÙ ys–€ are“`Âid¹',“for“the“transaction“ID,“and“`Âtime¹',“for“the“time“at“which“the“transaction“w˜as“committed.Ž¦¤  ‘sçÇ>>>–ffprint“storage.undoLog(0,“sys.maxint)Ž¡‘sç[{'description':‘ff'',Ž¡‘'@³'id':‘ff'AzpGEGqU/0QAAAAAAAAGMA',Ž¡‘'@³'time':‘ff981126744.98,Ž¡‘'@³'user_name':‘ff''},Ž¡‘!ÚM{'description':‘ff'',Ž¡‘'@³'id':‘ff'AzpGC/hUOKo!
AAAAAAAAFDQ',Ž¡‘'@³'time':‘ff981126478.202,Ž¡‘'@³'user_name':‘ff''}Ž¡‘'@³...ŽŸ  ŽŽ  ‚«¹T‘ÿ37o–œstore“a“description“and“a“user“name“on“a“commit,‘%Ãget“the“current“transaction“and“call“the“Ânote(¼teÌÐxt.Â)“¹method“toŽ¡store–ï¬a“description,‘—and“the“ÂsetUser(¼user‘ ™˜‰  ff €Ž‘šnameÂ)“¹method“to“store“the“user“name.‘hœWhile“ÂsetUser()“¹o•Ù v“erwritesŽ¡the–MPcurrent“user“name“and“replaces“it“with“the“nešÀ w“v˜alue,‘Wsthe“Ânote()“¹method“al•ægw“ays–MPadds“the“teÙ xt“to“the“transaction'‘ÿs8sŽ¡description,‘U³so–K it“can“be“called“sešÀ vÙ eral“times“to“log“se˜vÙ eral“dierent“changes“made“in“the“course“of“a“single“transaction.Ž¦¤  ‘sçÇget_transaction().setUser('amk')Ž¡‘sçget_transaction().note('Change‘ffownership')ŽŸ  ŽŽŸ8‰¹T‘ÿ37o–©èundo“a“transaction,›´bcall“the“ÂDB.undo(¼idEÂ)“¹method,˜passing“it“the“ID‘©Ýof“the“transaction“to“undo.‘—QIf“the“transactionŽ¡can'Ñðt–c{be“undone,›i/a“ÂZODB.POSException.UndoError“¹eÙ xception“will“!
be“raised,˜with“the“message“\non-undoableŽ¡transaction".‘õ9Usually–ãthis“will“happen“because“later“transactions“modied“the“objects“aected“by“the“transaction“you'‘ÿÿreŽ¡trying–€ to“undo.ŽŸ"PÃÄ4.3Ž‘®V‘ÿ
BersionsŽŸpŹWhile–&manšÙ y“subtransactions“can“be“contained“within“a“single“re˜gular“transaction,‘Oit'‘ÿs8s“also“possible“to“contain“man˜yŽ¡rešÙ gular– àtransactions“within“a“long-running“transaction,‘!called“a“v˜ersion“in“ZODB‘ ¿terminology‘ÿY .‘œ8Inside“a“v˜ersion,‘!an˜yŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å4.2‘
  Undoing‘ǧChangesŽŽŽ’Ê¢˜17ŽŽŽŽŽŽŽŽŒ‹                                        '¦ ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ì¹number–Æof“transactions“can“be“created“and“committed“or“rolled“back,‘)·bÌÐut“the“changes“within“a“vÙ ersion“are“not“madeŽ¤  visible–€ to“other“connections“to“the“same“ZODB.Ž©°ÇNot–mÑall“storages“support“všÙ ersions,‘qtbÌÐut“you“can“test“for“v˜ersioning“ability“by“calling“ÂsupportsVersions()“¹methodŽ¡of–€ the“ÂDB“¹instance,“which“returns“true“if“the“underlying“storage“supports“vÙ ersioning.Ž¦A‘ïXvÙ ersion–ïtcan“be“selected“when“creating“the“ÂConnection“¹instance“using“the“ÂDB.open(¿[¼verægsion‘l1¿]Â)“¹method.‘gõTheŽ¡¼verægsion–€ ¹arÑðgument“must“be“a“string“that“will“be“used“as“the“name“of“the“vÙ ersion.Ž¦Ÿ  ‘sçÇvers_conn–ff=“db.open(version='Working“version')ŽŸ  ŽŽŸ-‚«¹T¦gransactions–?Ycan“then“be“committed“and“aborted“using“this“vÙ ersioned“connection.‘W¤Other“connections“that“don'ÑðtŽ¡specify–ºÌa“všÙ ersion,‘!
	or“pro˜vide“a“dierent“v˜ersion“name,‘	will“not“see“changes“committed“within“the“v˜ersion“namedŽ¡`ÂWorking‘  version¹'.‘
T‘ÿ37o–ZWcommit“or“abort“a“vÙ ersion,‘aßwhich“will“either“makæge“the“changes“visible“to“all“clients“or“rollŽ¡them–hback,‘$Acall“the“ÂDB.commitVersion()“¹or“ÂDB.abortVersion()“¹methods.‘£ÏXXX‘Fwhat“are“the“source“andŽ¡dest–€ arÑðguments“for?Ž¦The–ãàZODB‘ãÆmakæges“no“attempt“to“reconcile“changes“between“dierent“všÙ ersions.‘E7Instead,‘üØthe“rst“v˜ersion“which“mod-Ž¡ies–5Âan“object“will“gó7ain“a“lock“on“that“object.‘:ÞAttempting“to“modify“the“object“from“a“dierent“vÙ ersion“or“from“anŽ¡un™ŸvÙ ersioned–€ connection“will“cause“a“ÂZODB.POSException.VersionLockError“¹to“be“raised:Ž¦¤  ‘sçÇfrom–ffZODB.POSException“import“VersionLockErrorŽ¡¡‘sçtry:Ž¡‘2
get_transaction().commit()Ž¡‘sçexcept–ffVersionLockError,“(obj_id,“version):Ž¡‘2
print–ff('Cannot“commit;“object“%s“'Ž¡‘WÚI'locked–ffby“version“%s'“%“(obj_id,“version)“)ŽŸ  ŽŽŸo‚«¹The–€ ešÙ xception“pro˜vides“the“ID“of“the“lockæged“object,“and“the“name“of“the“v˜ersion“haÌÐving“a“l!
ock“on“it.ŽŸ"PÃÄ4.4Ž‘®Multithreaded–UüZODB“Prog•áIr“amsŽŸpŹZODB›°§databases–°öcan“be“accessed“from“multithreaded“Python“programs.‘¬yThe“ÂStorage“¹and“ÂDB˜¹instances“can“beŽ¡shared–€ among“sešÀ vÙ eral“threads,“as“long“as“indi˜vidual“ÂConnection“¹instances“are“created“for“each“thread.Ž¦XXX–€ I“can'Ñðt“think“of“anšÙ ything“else“to“say“about“multithreaded“ZODB“programs.–˜Suggestions?“An–€ e˜xample“program?ŽŸ'pÃÀ5Ž‘hRelated‘ ÈModulesŽŸ
_¹The–õÂZODB‘õ¤package“includes“a“number“of“related“modules“that“prošÙ vide“useful“data“types“such“as“BT¦grees“or“full-te˜xtŽ¡inde•Ù x“es.ŽŸ"PÃÄ5.1Ž‘®ó'0ˆÛ    
   pcrr7tÒZODB.PersistentMappingŽŸpŹThe–ÂPersistentMapping“¹class“is“a“wrapper“for“mapping“objects“that“will“set“the“dirty“bit“when“the“mapping“isŽ¡modied–€ by“setting“or“deleting“a“kægeÙ y‘ÿY .Ž¦ó(0ˆÛ 
   
   pcrb7tÓPersistentMappingÂ(¼container–€ =“{}‘‡§Â)ŽŽŽŽ¡‘ ¹Create–‹la“ÂPersistentMapping“¹object“that“wraps“the“mapping“object“¼container:ß¹.‘;ÛIf“you“don'Ñðt“specify“a“vÀ alueŽ¡‘ for–€ ¼container:ß¹,“a“reÙ gular“Python“dictionary“is“used.ŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å18ŽŽŽ’{*z5‘
  Related‘ǧModulesŽŽŽŽŽŽŽŽŒ‹                                        4ù ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ìÂPersistentMapping–€ ¹objects“support“all“the“same“methods“as“Python“dictionaries“do.ŽŸ!ÀÄ5.2Ž‘®ÒZODB.PersistentListŽŸpŹThe–¯6ÂPersistentList“¹class“is“a“wrapper“for“mutable“sequence“objects,‘ûmuch“as“ÂPersistentMapping“¹is“aŽ¤  wrapper–€ for“mappings.Ž©°ÇÓPersistentListÂ(¼initlist–€ =“[]Â)ŽŽŽŽ¡‘ ¹Create–/Ùa“ÂPersistentList“¹object“that“wraps“the“mutable“sequence“object“¼initlist.¹.‘þáIf“you“don'Ñðt“specify“a“vÀ alueŽ¡‘ for–€ ¼initlist.¹,“a“reÙ gular“Python“list“is“used.Ž¦ÂPersistentList–€ ¹objects“support“all“the“same“methods“as“Python“lists“do.ŽŸ!ÀÄ5.3Ž‘®B-tree‘UüModulesŽŸpŹWhen–(programming“with“the“ZODB,“Python“dictionaries“aren'Ñðt“al•ægw“ays–(what“you“need.‘äThe“most“important“case“isŽ¡where–H>you“wægant“to“store“a“vÙ ery“larÑðge“mapping.‘When“a“Python“dictionary“is“accessed“in“a“ZODB,“the“whole“dict!
ionaryŽ¡has–óto“be“unpickled“and“brought“into“memory‘ÿY .‘äqIf“you'‘ÿÿre“storing“something“vÙ ery“larÑðge,‘?/such“as“a“100,000-entry“userŽ¡database,‘¤punpickling–iôsuch“a“larÑðge“object“will“be“sloÀ w‘ÿY .‘×tB-trees“are“a“balanced“tree“data“structure“that“behaÌÐvÙ e“likæge“aŽ¡mapping–©zbšÌÐut“distrib˜ute“kægeÙ ys“throughout“a“number“of“tree“nodes.‘–Nodes“are“then“only“unpickled“and“brought“intoŽ¡memory–Øas“thešÙ y'‘ÿÿre“accessed,‘îso“the“entire“tree“doesn'Ñðt“haÌÐv˜e“to“occupægy“memory“(unless“you“really“are“touching“eÀ v˜eryŽ¡single‘€ kægeÙ y).Ž¦There–þare“four“dierent“BT¦gree“modules“proÙ vided.‘“£One“of“them,›„the“ÂBTree“¹module,˜proÙ vides“the“most“general“dataŽ¡type;‘XIthe–1kægešÙ ys“and“vÀ alues“in“the“B-tree“can“be“an˜y“Python“object.‘Ê*Some“specialized“B-tree“modules“require“that“theŽ¡kægešÙ ys,–€ and“perhaps“eÀ v˜en“the“vÀ alues,“to“be“of“a“certain“type,“and“pro˜vide“fægaster“performance“because“of“this“lim!
itation.Ž©Œª‘ç€ÂIOBTreeŽŽŽ‘ ¹requires–ƒ;the“kægešÙ ys“to“be“inte˜gers.‘#JThe“module“name“reminds“you“of“this;‘„Ùthe“ÂIOBTree“¹module“maps“Inte˜gers“toŽ¡‘ Objects.Ž¤¾Z‘ç€ÂOIBTreeŽŽŽ‘ ¹requires–€ the“vÀ alues“to“be“intešÙ gers,“mapping“Objects“to“Inte˜gers.Ž¡‘ç€ÂIIBTreeŽŽŽ‘ ¹is–€ strictest,“requiring“that“both“kægešÙ ys“and“vÀ alues“must“be“inte˜gers.Ž¦T‘ÿ37o–Å6use“a“B-tree,›Öƒsimply“import“the“desired“module“and“call“the“constructor™Ÿ,˜al•ægw“ays–Å6named“ÂBTree()¹,˜to“get“a“B-treeŽ¤  instance,–€ and“then“use“it“likæge“anÙ y“other“mapping:Ž©°Ç‘sçÇimport‘ffIIBTreeŽ¤  ‘sçiimap–ff=“IIBTree.BTree()Ž¡‘sçiimap[1972]–ff=“27ŽŸ  ŽŽŸKGjÀAŽ‘2ResourcesŽŸ
_¹ZODB–€ HO¦gWTÑðO,“by“Michel“Pelletier:Ž¡Goes–€ into“slightly“more“detail“about“the“rules“for“writing“applications“using“the“ZODB.Ž¡Èhttp://www‘ÿuÃ.z•Ývope“.org/Members/michel/Ho“wT›þëˆos/ZODB-Ho“w-T˜oŽŽ¦¹Introduction–€ to“the“Zope“Object“Database,“by“Jim“Fulton:Ž¡Goes–Kinto“much“greater“detail,‘UšeÙ xplaining“advšÀ anced“uses“of“the“ZODB‘Jóand“ho˜w“it'‘ÿs8s“actually“implemented.‘îA‘Jódeniti˜vÙ eŽ¡reference,–€ and“highly“recommended.Ž¡Èhttp://www‘ÿuÃ.pºåython.org/wèöor"‰kshops/2000-01/proceedings/papers/fulton/zÝvodb3.htmlŽŽ¦¹DoÀ wnload–€ link“for“ZEO:Ž¡Èhttp://www‘ÿuÃ.z•Ývope“.org/Products/ZEO/ŽŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å5.2‘
  ÂZODB.PersistentListŽŽŽ’Ê¢˜Å19ŽŽŽŽŽŽŽŽŒ‹                                        A¯ ¨n ý>‘ìŸüfdŽŽŽŽ £n ý€.ÌÀBŽ‘2GNU– ÈF‘ÿZ!ree“Documentation“LicenseŽŸ
_¹V‘þã×ersion–€ 1.1,“March“2000Ž¤  ©°ÇCopšægyright‘G±ž²×cŽŽŽ‘€ ¸
ŽŽŽŽ‘ ¹2000–€ Free“Softw˜are“FÙ oundation,“Inc.Ž¡59–€ T‘ÿLÐemple“Place,“Suite“330,“Boston,“MA“02111-1307“USAŽ¡EvšÙ eryone–€ is“permitted“to“copægy“and“distribÌÐute“v˜erbatim“copies“of“this“license“document,“bÌÐut“changing“it“is“not“alloÀ wed.ŽŸ!×&ÄPreamb“leŽŸpŹThe––purpose“of“this“License“is“to“makæge“a“manual,–›ŸteÙ xtbook,“or––other“written“document“\free"“in“the“sense“of“freedom:Ž¡to–tHassure“ešÀ vÙ eryone“the“eecti˜vÙ e“freedom“to“copægy“and“redistribÌÐute“it,›v with“or“without“modifying“it,˜either“commerciallyŽ¡or›‡noncommercially–ÿY .‘øÅSecondarily“,‘19this˜License˜preservÙ es˜for˜the˜author˜and˜publisher˜a˜w•ægay˜to˜get˜credit˜for˜their˜w“ork,Ž¡while–€ not“being“considered“responsible“for“modications“made“by“others.Ž¦This–ÆLicense“is“a“kind“of“\copšægyleft",‘28which“means“that“deri•À v“ati“vÙ e–Æw˜orks“of“the“document“must“themselvÙ es“be“fr!
ee“in“theŽ¡same–€ sense.‘˜It“complements“the“GNU“General“Public“License,“which“is“a“copšægyleft“license“designed“for“free“softw˜are.Ž¦W‘ÿ37e–ÎuhaÌÐvÙ e“designed“this“License“in“order“to“use“it“for“manuals“for“free“softwšægare,‘"because“free“softw˜are“needs“freeŽ¡documentation:‘ãla–äêfree“program“should“come“with“manuals“proÙ viding“the“same“freedoms“that“the“softwægare“does.‘HVButŽ¡this–êLicense“is“not“limited“to“softwægare“manuals;‘eit“can“be“used“for“anšÙ y“te˜xtual“wægork,‘”re˜gó7ardless“of“subject“matterŽ¡or–~whether“it“is“published“as“a“printed“book.‘ÐW‘ÿ37e“recommend“this“License“principally“for“wægorks“whose“purpose“isŽ¡instruction–€ or“reference.ŽŸ!×&ÄB“.1Ž‘Å’Applicability–Uüand“DenitionsŽŸpŹThis–AòLicense“applies“to“anÙ y“manual“or“other“wšægork“that“contains“a“notice“placed“by“the“cop˜yright“holder“saying“it“can“beŽ¡distribÌÐuted–páunder“the“terms“of“this“License.›ŽThe“\Document",–sçbeloÀ w‘ÿY ,“refers–páto“anÙ!
 y“such“manual“or“wægork.˜AnÙ y“memberŽ¡of–€ the“public“is“a“licensee,“and“is“addressed“as“\you".Ž¦A‘W>\Modied–WuV‘þã×ersion"“of“the“Document“means“anÙ y“wægork“containing“the“Document“or“a“portion“of“it,‘Reither“copiedŽ¡vÙ erbatim,–€ or“with“modications“and/or“translated“into“another“language.Ž¦A‘[\Secondary–[DSection"“is“a“named“appendix“or“a“front-matter“section“of“the“Document“that“deals“e•Ù xclusiÀ v“ely‘[DwithŽ¡the–Úrelationship“of“the“publishers“or“authors“of“the“Document“to“the“Document'‘ÿs8s“o•Ù v“erall–Úsubject“(or“to“related“matters)Ž¡and–=žcontains“nothing“that“could“fægall“directly“within“that“o•Ù v“erall›=žsubject.‘Rs(F“or˜e“xample,‘mif˜the˜Document˜is˜in˜part˜aŽ¡tešÙ xtbook–š¦of“mathematics,‘¡Pa“Secondary“Section“may“not“e˜xplain“an˜y“mathematics.)‘i‹The“relationship“could“be“a“matterŽ¡of–ƒÆhistorical“connection“with“the“subject“or“with“related“matters,›Ä·or“of“leÙ gó7al,˜commercial,˜philosophical,˜ethical“orŽ¡politi!
cal–€ position“reÙ gó7arding“them.Ž¦The–ËN\Inš™ŸvÀ ariant“Sections"“are“certain“Secondary“Sections“whose“titles“are“designated,‘Þ!as“being“those“of“In˜vÀ ariant“Sec-Ž¡tions,–€ in“the“notice“that“says“that“the“Document“is“released“under“this“License.Ž¦The›¸O\Co•Ù v“er˜T‘ÿLÐe“xts"˜are˜certain˜short˜passages˜of˜te“xt˜that˜are˜listed,‘Æbas˜Front-Co“v“er˜T‘ÿLÐe“xts˜or˜Back-Co“v“er˜T‘ÿLÐe“xts,‘Æbin˜theŽ¡notice–€ that“says“that“the“Document“is“released“under“this“License.Ž¦A‘Àå\T¦gransparent"–Àöcopšægy“of“the“Document“means“a“machine-readable“cop˜y‘ÿY ,‘Ñ3represented“in“a“format“whose“specicationŽ¡is–NÈaÌÐvšÀ ailable“to“the“general“public,‘X whose“contents“can“be“vie˜wed“and“edited“directly“and“straightforwægardly“with“genericŽ¡tešÙ xt–òåeditors“or“(for“images“composed“of“pix˜els)“generic“paint“programs“or“(for“dra˜wings)“some“widely“aÌÐvÀ ailable“dra˜wingŽ¡editor™Ÿ,‘ Tand–æªthat“is“suitable“for“input“to“teÙ xt“formatters“or“for“automatic“transla!
tion“to“a“vÀ ariety“of“formats“suitable“forŽ¡input–:_to“teÙ xt“formatters.‘H´A‘:/copægy“made“in“an“otherwise“T¦gransparent“le“format“whose“markup“has“been“designed“toŽ¡thwšægart–h-or“discourage“subsequent“modication“by“readers“is“not“T¦gransparent.‘§A‘h'cop˜y“that“is“not“\T¦gransparent"“is“calledŽ¡\Opaque".Ž¦Examples–ù¤of“suitable“formats“for“T¦gransparent“copies“include“plain“ASCII‘ùCwithout“markup,‘XT‘ÿLÐeÙ xinfo“input“format,Ž¡LŸýþú‘üffÉAŽŽô;¹T‘þU>Ÿ?ÿEŽ‘1fX–,‚input“format,‘=5SGML›,mor“XML˜using“a“publicly“aÌÐvÀ ailable“DTD,“and“standard-conforming“simple“HTML˜designedŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å20ŽŽŽ’,´ïB‘
  GNU–ǧFŒÏree“Documentation“LicenseŽŽŽŽŽŽŽŽŒ‹                                        Nä ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ì¹for–0}human“modication.‘ÿOpaque“formats“include“PostScript,‘@dPDF‘ÿ37,“proprietary“formats“that“can“be“read“and“edited“onlyŽ¤  by–pproprietary“wægord“processors,‘"ÀSGML›Ror“XML˜for“which“the“DTD˜and/or“processing“tools“are“not“generally“aÌÐvÀ ailable,Ž¡and–€ the“machine-generated“HTML“produced“by“some“wægord“processors“for“output“purposes“only‘ÿY .Ž©°ÇThe–w\T¦gitle“PÙ age"“means,›´ãfor“a“printed“book,˜the“title“page“itself,˜plus“such“folloÀ wing“pages“as“are“needed“to“hold,Ž¡lešÙ gibly‘ÿY ,‘’Õthe–[Þmaterial“this“License“requires“to“appear“in“the“title“page.‘­1F˜or“wægorks“in“formats“which“do“not“haÌÐv˜e“an˜yŽ¡title–ÛÄpage“as“such,‘òµ\T¦gitle“PšÙ age"“means“the“te˜xt“near“the“most“prominent“appearance“of“the“wægork'‘ÿs8s“title,‘òµpreceding“theŽ¡bešÙ ginning–€ of“the“body“of“the“te˜xt.ŽŸ"PÃÄB“.2Ž‘Å’V‘ÿ
Berbatim‘UüCop£ÜyingŽŸpŹY‘þægou–may“copægy“and“distribÌÐute“the“Document“in“anÙ y“medium,›%%either“commercially“or“noncommercially‘ÿY ,˜proÙ vided“thatŽ¡this–ƒ9License,›„the“copægyright“notices,˜and“the“license“notice“saying“this“License“applies“to“the“Document“are“reproducedŽ¡in–;0all“copies,‘iüand“that“you“add“no“other“conditions“whatsoeÀ vÙ er“to“those“of“this“License.‘K)Y‘þægou“may“not“use“technicalŽ¡measures–Nÿto“obstruct“or“control“the“reading“or“further“copšægying“of“the“copies“you“mak˜e“or“distribÌÐute.‘	CHo•À we“vÙ er™Ÿ,‘XÌyou‘NÿmayŽ¡accept–µŸcompensation“in“eÙ xchange“for“copies.‘ºuIf“you“distribÌÐute“a“larÑðge“enough“number“of“copies“you“must“also“folloÀ wŽ¡the–€ conditions“in“section“3.Ž¦Y‘þægou–€ may“also“lend“copies,“under“the“same“conditions“stated“abo•Ù v“e,–€ and“you“may“publicly“display“copies.ŽŸ"PÃÄB“.3Ž‘Å’Cop£Üying–Uüin“QuantityŽŸpŹIf–žjyou“publish“printed“copies“of“the“Document“numbering“more“than“10!
0,‘¦and“the“Document'‘ÿs8s“license“notice“requiresŽ¡Co•Ù v“er›²T‘ÿLÐe“xts,‘¾…you˜must˜enclose˜the˜copies˜in˜co“v“ers˜that˜carry‘ÿY ,‘¾…clearly˜and˜le“gibly‘ÿY ,‘¾…all˜these˜Co“v“er˜T‘ÿLÐe“xts:‘}¡Front-Co“v“erŽ¡T‘ÿLÐešÙ xts–·Áon“the“front“co˜v˜er™Ÿ,‘űand“Back-Co˜v˜er“T‘ÿLÐe˜xts“on“the“back“co˜v˜er‘ÿs8.‘ÀÜBoth“co˜v˜ers“must“also“clearly“and“le˜gibly“identifyŽ¡you–y6as“the“publisher“of“these“copies.‘:The“front“co•Ù v“er–y6must“present“the“full“title“with“all“wægords“of“the“title“equallyŽ¡prominent–"¬and“visible.››Y‘þægou“may“add“other“material“on“the“co•Ù v“ers–"¬in“addition.˜Copægying“with“changes“limited“to“theŽ¡co•Ù v“ers,‘»<as–|0long“as“thešÙ y“preserv˜e“the“title“of“the“Document“and“satisfy“these“conditions,‘»<can“be“treated“as“v˜erbatimŽ¡copægying–€ in“other“respects.Ž¦If–Àthe“required“tešÙ xts“for“either“co˜v˜er“are“too“vÌÐoluminous“to“t“le˜gibly‘ÿY ,‘Ðyou“should“put“the“rst“ones“listed“(as“man˜y“asŽ¡t–€ reasonably)“on“the“act!
ual“co•Ù v“er™Ÿ,–€ and“continue“the“rest“onto“adjacent“pages.Ž¦If–you“publish“or“distribÌÐute“Opaque“copies“of“the“Document“numbering“more“than“100,‘ÐIyou“must“either“include“aŽ¡machine-readable–ôT¦gransparent“copšægy“along“with“each“Opaque“cop˜y‘ÿY ,‘&1or“state“in“or“with“each“Opaque“cop˜y“a“publicly-Ž¡accessible–¸9computerÌÐ-netwšægork“location“containing“a“complete“T¦gransparent“cop˜y“of“the“Document,‘ÆGfree“of“added“mate-Ž¡rial,‘Šåwhich–ˆ·the“general“netwægork-using“public“has“access“to“doÀ wnload“anonÙ ymously“at“no“charÑðge“using“public-standardŽ¡netwšægork–=Qprotocols.‘^If“you“use“the“latter“option,‘J§you“must“tak˜e“reasonably“prudent“steps,‘J§when“you“beÙ gin“distribÌÐution“ofŽ¡Opaque–˜Žcopies“in“quantity‘ÿY ,‘ž²to“ensure“that“this“T¦gransparent“copægy“will“remain“thus“accessible“at“the“stated“location“untilŽ¡at–Œleast“one“year“after“the“last“time“you“distribÌÐute“an“Opaque“copægy“(directly“or“through“your“agents“or“retailer!
s)“of“thatŽ¡edition–€ to“the“public.Ž¦It–fÏis“requested,› ‚bÌÐut“not“required,˜that“you“contact“the“authors“of“the“Document“well“before“redistribÌÐuting“anÙ y“larÑðgeŽ¡number–€ of“copies,“to“giÀ všÙ e“them“a“chance“to“pro˜vide“you“with“an“updated“v˜ersion“of“the“Document.ŽŸ"PÃÄB“.4Ž‘Å’ModicationsŽŸpŹY‘þægou–Wmay“copægy“and“distribÌÐute“a“Modied“V‘þã×ersion“of“the“Document“under“the“conditions“of“sections“2“and“3“abo•Ù v“e,Ž¡proÙ vided–nzthat“you“release“the“Modied“V›þã×ersion“under“precisely“this“License,‘qûwith“the“Modied“V˜ersion“lling“the“roleŽ¡of–n*the“Document,‘q»thus“licensing“distribÌÐution“and“modication“of“the“Modied“V‘þã×ersion“to“whoeÀ vÙ er“possesses“a“copægy“ofŽ¡it.‘˜In–€ addition,“you“must“do“these“things“in“the“Modied“V‘þã×ersion:ŽŸ°Ç‘ ¸ŽŽŽ‘ ¹Use–
ˆin“the“T¦gitle“PšÙ age“(and“on“the“co˜v˜ers,‘0éif“an˜y)“a“title“distinct“from“that“of“the“Document,‘0éand“from“those“ofŽ¡‘ preÀ vious–vvšÙ ersions“(!
which“should,‘¤Óif“there“were“an˜y‘ÿY ,‘¤Óbe“listed“in“the“History“section“of“the“Document).‘qùY‘þægou“mayŽŽŸ  Ÿô  ‰  ffÕÁHŸ  ÅBÌÐ.2‘
  V‘ÿ37erbatim‘ǧCop³7yingŽŽŽ’Ê¢˜21ŽŽŽŽŽŽŽŽŒ‹                                        a ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ì‘ ¹use–€ the“same“title“as“a“preÀ vious“všÙ ersion“if“the“original“publisher“of“that“v˜ersion“giÀ v˜es“permission.Ž©  ‘ ¸ŽŽŽ‘ ¹List–CÇon“the“T¦gitle“PÙ age,›OÓas“authors,˜one“or“more“persons“or“entities“responsible“for“authorship“of“the“modicationsŽ¤  ‘ in– Qthe“Modied“V‘þã×ersion,‘¨etogether“with“at“least“À vÙ e“of“the“principal“authors“of“the“Document“(all“of“its“principalŽ¡‘ authors,–€ if“it“has“less“than“À vÙ e).Ž¦‘ ¸ŽŽŽ‘ ¹State–€ on“the“T¦gitle“page“the“name“of“the“publisher“of“the“Modied“V‘þã×ersion,“as“the“publisher‘ÿs8.Ž¦‘ ¸ŽŽŽ‘ ¹PreservÙ e–€ all“the“copægyright“notices“of“the“Document.Ž¦‘ ¸ŽŽŽ‘ ¹Add–€ an“appropriate“copšægyright“notice“for“your“modications“adjacent“to“the“other“cop˜yright“notices.Ž¦‘ ¸ŽŽŽ‘ ¹Include,›˜¢immediately–“µafter“the“copægyright“no!
tices,˜a“license“notice“giÀ ving“the“public“permission“to“use“the“Modi-Ž¡‘ ed–€ V‘þã×ersion“under“the“terms“of“this“License,“in“the“form“shošÀ wn“in“the“Addendum“belo˜w‘ÿY .Ž¦‘ ¸ŽŽŽ‘ ¹PreservšÙ e–win“that“license“notice“the“full“lists“of“In™ŸvÀ ariant“Sections“and“required“Co˜v˜er“T‘ÿLÐe˜xts“giÀ v˜en“in“the“Docu-Ž¡‘ ment'‘ÿs8s–€ license“notice.Ž¦‘ ¸ŽŽŽ‘ ¹Include–€ an“unaltered“copægy“of“this“License.Ž¦‘ ¸ŽŽŽ‘ ¹PreservÙ e–fåthe“section“entitled“\History",› žand“its“title,˜and“add“to“it“an“item“stating“at“least“the“title,˜year™Ÿ,˜neÀ wŽ¡‘ authors,‘&Rand–çpublisher“of“the“Modied“V‘þã×ersion“as“giÀ všÙ en“on“the“T¦gitle“P˜age.‘ô;If“there“is“no“section“entitled“\History"Ž¡‘ in–ºžthe“Document,›ÉFcreate“one“stating“the“title,˜year™Ÿ,˜authors,˜and“publisher“of“the“Document“as“giÀ vÙ en“on“its“T¦gitleŽ¡‘ PÙ age,–€ then“add“an“item“describing“the“Modied“V‘þã×ersion“as“stated“in“the“preÀ vious“sentence.Ž¦‘ !
¸ŽŽŽ‘ ¹PreservšÙ e–~àthe“netwægork“location,‘¾—if“an˜y‘ÿY ,‘¾—giÀ v˜en“in“the“Document“for“public“access“to“a“T¦gransparent“copægy“of“theŽ¡‘ Document,‘‡©and–RîlikšægeÀ wise“the“netw˜ork“locations“gišÀ vÙ en“in“the“Document“for“pre˜vious“vÙ ersions“it“wægas“based“on.Ž¡‘ These–,pmay“be“placed“in“the“\History"“section.‘ý¾Y‘þægou“may“omit“a“netwšægork“location“for“a“w˜ork“that“w˜as“published“atŽ¡‘ least–rfour“years“before“the“Document“itself,‘ [or“if“the“original“publisher“of“the“všÙ ersion“it“refers“to“giÀ v˜es“permission.Ž¦‘ ¸ŽŽŽ‘ ¹In–ùþanšÙ y“section“entitled“\‘ÿ37AcknoÀ wledgements"“or“\Dedications",‘}preserv˜e“the“section'‘ÿs8s“title,‘}and“preserv˜e“in“theŽ¡‘ section–-all“the“substance“and“tone“of“each“of“the“contribÌÐutor“acknošÀ wledgements“and/or“dedications“gi˜vÙ en“therein.Ž¦‘ ¸ŽŽŽ‘ ¹PreservšÙ e–ÏËall“the“In™ŸvÀ ariant“Sections“of“the“Document,‘ã½unaltered“in“their“te˜xt“and“in“their“titles.‘ùSection“!
numbersŽ¡‘ or–€ the“equi•À v“alent–€ are“not“considered“part“of“the“section“titles.Ž¦‘ ¸ŽŽŽ‘ ¹Delete–€ anÙ y“section“entitled“\Endorsements".‘˜Such“a“section“may“not“be“included“in“the“Modied“V‘þã×ersion.Ž¦‘ ¸ŽŽŽ‘ ¹Do–€ not“retitle“anšÙ y“e˜xisting“section“as“\Endorsements"“or“to“con
ict“in“title“with“an˜y“In™ŸvÀ ariant“Section.ŽŸ°ÇIf–åthe“Modied“V‘þã×ersion“includes“neÀ w“front-matter“sections“or“appendices“that“qualify“as“Secondary“Sections“andŽ¡contain–jno“material“copied“from“the“Document,‘¤‘you“may“at“your“option“designate“some“or“all“of“these“sections“asŽ¡inš™ŸvÀ ariant.‘ŸcT‘ÿ37o–îdo“this,‘"jadd“their“titles“to“the“list“of“In˜vÀ ariant“Sections“in“the“Modied“V‘þã×ersion'‘ÿs8s“license“notice.‘ŸcTheseŽ¡titles–€ must“be“distinct“from“anÙ y“other“section“titles.Ž©°ÇY‘þægou–f:may“add“a“section“entitled“\Endorsements",‘kbproÙ vided“it“contains“nothing“bšÌÐut“endorsements“of“your“Modied“V‘þã×er˜-Ž¡sion–Ž§by“všÀ ario!
us“parties“{“for“eÙ xample,‘’Qstatements“of“peer“re˜vie˜w“or“that“the“tešÙ xt“has“been“appro˜v˜ed“by“an“orÑðgó7anizationŽ¡as–€ the“authoritatiÀ vÙ e“denition“of“a“standard.Ž¦Y‘þægou–×cmay“add“a“passage“of“up“to“À všÙ e“wægords“as“a“Front-Co˜v˜er“T‘ÿLÐe˜xt,‘í<and“a“passage“of“up“to“25“wægords“as“a“Back-Co˜v˜erŽ¡T‘ÿLÐešÙ xt,‘c|to–5ýthe“end“of“the“list“of“Co˜v˜er“T‘ÿLÐe˜xts“in“the“Modied“V‘þã×ersion.‘;Only“one“passage“of“Front-Co˜v˜er“T‘ÿLÐe˜xt“and“oneŽ¡of›=ÍBack-Co•Ù v“er˜T‘ÿLÐe“xt˜may˜be˜added˜by˜(or˜through˜arrangements˜made˜by)˜an“y˜one˜entity‘ÿY .‘RþIf˜the˜Document˜alreadyŽ¡includes–Bëa“co•Ù v“er›Bëte“xt˜for˜the˜same˜co“v“er™Ÿ,‘s¦preÀ viously˜added˜by˜you˜or˜by˜arrangement˜made˜by˜the˜same˜entity˜youŽ¡are–Ÿacting“on“behalf“of,›(Fyou“may“not“add“another;‘IîbÌÐut“you“may“replace“the“old“one,˜on“eÙ xplicit“permission“from“theŽ¡preÀ vious–€ publisher“that“added“the“old“one.Ž¦The–;Òauthor(s)“and“publisher(s)“of“the“Document“do“not“by“t!
his“License“giÀ vÙ e“permission“to“use“their“names“for“publicityŽ¡for–€ or“to“assert“or“imply“endorsement“of“anÙ y“Modied“V‘þã×ersion.ŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å22ŽŽŽ’,´ïB‘
  GNU–ǧFŒÏree“Documentation“LicenseŽŽŽŽŽŽŽŽŒ‹                                        u ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ìÄB“.5Ž‘Å’Combining‘UüDocumentsŽŸpŹY‘þægou–[mmay“combine“the“Document“with“other“documents“released“under“this“License,‘b½under“the“terms“dened“in“sectionŽ¤  4›»abo•Ù v“e˜for˜modied˜v“ersions,‘ÉÈpro“vided˜that˜you˜include˜in˜the˜combination˜all˜of˜the˜In™ŸvÀ ariant˜Sections˜of˜all˜of˜theŽ¡original–€ documents,“unmodied,“and“list“them“all“as“In™ŸvÀ ariant“Sections“of“your“combined“wægork“in“its“license“notice.Ž©°ÇThe–Scombined“wšægork“need“only“contain“one“cop˜y“of“this“License,‘Á§and“multiple“identical“In™ŸvÀ ariant“Sections“may“beŽ¡replaced–ÀZwith“a“single“copšægy‘ÿY .‘Ú¥If“there“are“multiple“In™ŸvÀ ariant“Sections“with“the“same“name“bÌÐut“dierent“contents,‘Ðpmak˜eŽ¡the–?title“of“each“such“section“unique“by“adding“at“the“end“of“it,›ÁŽin“parentheses,˜the“name“of“the“original“author“orŽ¡publisher–µof“that“!
section“if“knoÀ wn,‘ÂYor“else“a“unique“number‘ÿs8.‘¸ÔMakæge“the“same“adjustment“to“the“section“titles“in“the“listŽ¡of–€ In™ŸvÀ ariant“Sections“in“the“license“notice“of“the“combined“wægork.Ž¦In–‘Õthe“combination,›ÖJyou“must“combine“anÙ y“sections“entitled“\History"“in“the“vÀ arious“original“documents,˜formingŽ¡one–ZØsection“entitled“\History";‘g;likægešÀ wise“combine“anÙ y“sections“entitled“\‘ÿ37Ackno˜wledgements",‘bFand“anÙ y“sections“entitledŽ¡\Dedications".‘˜Y‘þægou–€ must“delete“all“sections“entitled“\Endorsements.‘ÿLÐ"ŽŸ!ÝÈÄB“.6Ž‘Å’Collections–Uüof“DocumentsŽŸpŹY‘þægou–‡kmay“makæge“a“collection“consisting“of“the“Document“and“other“documents“released“under“this“License,‘‰Eand“replaceŽ¡the–@hindišÀ vidual“copies“of“this“License“in“the“v˜arious“documents“with“a“single“copægy“that“is“included“in“the“collection,Ž¡prošÙ vided–€ that“you“folloÀ w“the“rules“of“this“License“for“v˜erbatim“copægying“of“each“of“the“documents“in“all“other“r!
espects.Ž¦Y‘þægou–¬may“eÙ xtract“a“single“document“from“such“a“collection,›·Äand“distribÌÐute“it“indiÀ vidually“under“this“License,˜proÙ videdŽ¡you–Øoinsert“a“copægy“of“this“License“into“the“ešÙ xtracted“document,‘î‹and“folloÀ w“this“License“in“all“other“respects“re˜gó7ardingŽ¡vÙ erbatim–€ copægying“of“that“document.ŽŸ!ÝÈÄB“.7Ž‘Å’AggáIregation–UüWith“Independent“W£Üor.ksŽŸpŹA‘ìácompilation–ìþof“the“Document“or“its“deri•À v“ati“vÙ es–ìþwith“other“separate“and“independent“documents“or“wægorks,‘=in“or“onŽ¡a–qtvšÌÐolume“of“a“storage“or“distrib˜ution“medium,‘­Ðdoes“not“as“a“whole“count“as“a“Modied“V‘þã×ersion“of“the“Document,Ž¡prošÙ vided–øno“compilation“copægyright“is“claimed“for“the“compilation.‘ÍSuch“a“compilation“is“called“an“\aggre˜gó7ate",‘andŽ¡this–÷¦License“does“not“apply“to“the“other“self-contained“wægorks“thus“compiled“with“the“Document,‘on“account“of“theirŽ¡being–€ thus“compiled,“if“thešÙ y“are“not“themselv˜es!
“deri•À v“ati“v˜e–€ wægorks“of“the“Document.Ž¦If–’the“Co•Ù v“er›’T‘ÿLÐe“xt˜requirement˜of˜section˜3˜is˜applicable˜to˜these˜copies˜of˜the˜Document,‘—0then˜if˜the˜Document˜is˜lessŽ¡than–¢Òone“quarter“of“the“entire“aggrešÙ gó7ate,‘«‡the“Document'‘ÿs8s“Co˜v˜er“T‘ÿLÐe˜xts“may“be“placed“on“co˜v˜ers“that“surround“only“theŽ¡Document–€ within“the“aggrešÙ gó7ate.‘˜Otherwise“the˜y“must“appear“on“co˜v˜ers“around“the“whole“aggre˜gó7ate.Ž©!ÝÈÄB“.8Ž‘Å’T‘þ`ráIanslationŽŸpŹT¦granslation–¾íis“considered“a“kind“of“modication,‘Ψso“you“may“distribÌÐute“translations“of“the“Document“under“the“termsŽ¡of–Æ’section“4.‘íMReplacing“In™ŸvÀ ariant“Sections“with“translations“requires“special“permission“from“their“copægyright“holders,Ž¡bÌÐut–Dýyou“may“include“translations“of“some“or“all“Inš™ŸvÀ ariant“Sections“in“addition“to“the“original“vÙ ersions“of“these“In˜vÀ ariantŽ¡Sections.‘f Y‘þægou–™xmay“include“a“translation“of“this“License“prošÙ vided“that“you“als!
o“include“the“original“English“v˜ersion“ofŽ¡this–¾License.‘§ÓIn“case“of“a“disagreement“between“the“translation“and“the“original“English“vÙ ersion“of“this“License,‘%îtheŽ¡original–€ English“vÙ ersion“will“pre•À v“ail.Ž¦ÄB“.9Ž‘Å’T‘þ`erLÌminationŽŸpŹY‘þægou–9mmay“not“copægy–ÿY ,›G‹modify“,˜sublicense,˜or–9mdistribÌÐute“the“Document“ešÙ xcept“as“e˜xpressly“pro˜vided“for“under“this“License.Ž¡AnÙ y–7µother“attempt“to“copægy–ÿY ,›e¢modify“,˜sublicense–7µor“distribšÌÐute“the“Document“is“v˜oid,‘e¢and“will“automatically“terminateŽ¡your–e…rights“under“this“License.‘ÅHo•À we“všÙ er™Ÿ,‘jÑparties–e…who“haÌÐv˜e“receiÀ v˜ed“copies,›jÑor“rights,˜from“you“under“this“License“willŽ¡not–€ haÌÐvÙ e“their“licenses“terminated“so“long“as“such“parties“remain“in“full“compliance.ŽŽŸ  Ÿô  ‰  ffÕÁHŸ  ÅBÌÐ.5‘
  Combining‘ǧDocumentsŽŽŽ’Ê¢˜23ŽŽŽŽŽŽŽŽŒ‹                                        ‰Š ¨n ý>‘ìŸüfdŽŽŽŽ £n ý‘ìÄB“.10Ž‘$q•Future–UüRe£Üvisions“of“This“LicenceŽ©pŹThe–dîFree“Softwægare“FšÙ oundation“may“publish“ne•À w‘ÿY ,‘jXre“vised–dîv˜ersions“of“the“GNU‘dèFree“Documentation“License“from“timeŽ¤  to–jPtime.‘؇Such“neÀ w“všÙ ersions“will“be“similar“in“spirit“to“the“present“v˜ersion,‘¤ãbÌÐut“may“dier“in“detail“to“address“neÀ wŽ¡problems–€ or“concerns.‘˜See“Èhttp://www‘ÿuÃ.gnèöu.org/copºåyleft/Ž‘p¦6¹.ŽŸ°ÇEach–¢ävšÙ ersion“of“the“License“is“giÀ v˜en“a“distinguishing“v˜ersion“number‘ÿs8.‘‚DIf“the“Document“species“that“a“particularŽ¡numbered–ÔÑvšÙ ersion“of“this“License“"or“an˜y“later“v˜ersion"“applies“to“it,‘êyou“haÌÐv˜e“the“option“of“folloÀ wing“the“terms“andŽ¡conditions–óQeither“of“that“specied“všÙ ersion“or“of“an˜y“later“v˜ersion“that“has“been“published“(not“as“a“draft)“by“the“FreeŽ¡Softwægare–<˜FšÙ oundation.‘ If!
“the“Document“does“not“specify“a“v˜ersion“number“of“this“License,‘Jyou“may“choose“an˜y“v˜ersionŽ¡eÀ všÙ er–€ published“(not“as“a“draft)“by“the“Free“Softwægare“F˜oundation.ŽŸ"PÃÄADDENDUM:–UüHoÑów“to“use“this“License“f£Üor“y“our“documentsŽ¦¹T‘ÿ37o–Ôëuse“this“License“in“a“document“you“haÌÐvÙ e“written,‘*%include“a“copægy“of“the“License“in“the“document“and“put“theŽ¡folloÀ wing–€ copægyright“and“license“notices“just“after“the“title“page:Ž©°Ç‘ Copšægyright‘Ùëž²×cŽŽŽ‘:¸
ŽŽŽŽ‘$u¹YEAR–Y³7OUR“N¦gAME.–:Permission“is“granted“to“cop˜y‘ÿY ,‘6ÈdistribÌÐute“and/or“modify“this“docu-Ž¡‘ ment–:'under“the“terms“of“the“GNU‘:Free“Documentation“License,‘HV‘þã×ersion“1.1“or“anšÙ y“later“v˜ersion“publishedŽ¡‘ by–+)the“Free“Softwægare“FÙ oundation;‘Gqwith“the“In™ŸvÀ ariant“Sections“being“LIST–+THEIR“TITLES,–+)with“the“Front-Ž¡‘ Co•Ù v“er›”áT‘ÿLÐe“xts˜being˜LIST‘ÿB,˜and˜with˜the˜Back-Co“v“er˜T‘ÿLÐe“xts˜being˜LIST‘ÿB.˜A‘”Ücopægy˜!
of˜the˜license˜is˜included˜inŽ¡‘ the–€ section“entitled“\GNU“Free“Documentation“License".Ž¦If–.you“haÌÐvÙ e“no“Inš™ŸvÀ ariant“Sections,‘Y–write“\with“no“In˜vÀ ariant“Sections"“instead“of“saying“which“ones“are“in˜vÀ ariant.‘#ÎIfŽ¡you–FðhaÌÐvšÙ e“no“Front-Co˜v˜er“T‘ÿLÐe˜xts,‘RZwrite“\no“Front-Co˜v˜er“T‘ÿLÐe˜xts"“instead“of“\Front-Co˜v˜er“T‘ÿLÐe˜xts“being“LIST";“likægeÀ wise“forŽ¡Back-Co•Ù v“er‘€ T‘ÿLÐe“xts.ŽŸ°ÇIf–¾Çyour“document“contains“nontriÀ vial“ešÙ xamples“of“program“code,‘Îywe“recommend“releasing“these“e˜xamples“in“parallelŽ¡under–Ú4your“choice“of“free“softwægare“license,›0Ásuch“as“the“GNU‘ÙÛGeneral“Public“License,˜to“permit“their“use“in“freeŽ¡softwægare.ŽŽŸ  Ÿô  ‰  ffÕÁHŸ  Å24ŽŽŽ’,´ïB‘
  GNU–ǧFŒÏree“Documentation“LicenseŽŽŽŽŽŽŽŽŒø œKƒ’À;    謰ÕÁH 	 ó(0ˆÛ 
   
   pcrb7tó'0ˆÛ    
   pcrr7tó&3{Ù    
   ptmr7tó3{Ù    
   ptmr7tó3{Ù    
   ptmr7tó»ló 	   
   phvr7tó0ˆÛ 	   
   pcrr7tó»ló 
   
   phvr7tó»ló    
   phvr7tó0ˆÛ 
   
   pcrr7tóÓߌ˜ 
   
   ptmb7tó»ló ff 
   phvr7tó3{Ù ff 
   ptmr7tó»ló    
   phvro7tóKjà 
   
   ptmri7tó»ló áH 
   phvr7tó3{Ù 
   
   ptmr7tó
!",š 
   
   cmsy10ù ¦óßßßß

=== Added File StandaloneZODB/Doc/guide/zodb.tex ===
\documentclass{howto}

\title{ZODB/ZEO Programming Guide}
\release{0.02}
\date{\today}

\author{A.M.\ Kuchling}
\authoraddress{akuchlin@mems-exchange.org}

\begin{document}
\maketitle
\tableofcontents

\copyright{Copyright 2001 A.M. Kuchling.
      Permission is granted to copy, distribute and/or modify this document
      under the terms of the GNU Free Documentation License, Version 1.1
      or any later version published by the Free Software Foundation;
      with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
      A copy of the license is included in the appendix entitled ``GNU
      Free Documentation License''.}

\input{introduction}
\input{prog-zodb}
\input{zeo}
\input{transactions}
%\input{storages}
%\input{indexing}
%\input{admin}
\input{modules}

\appendix
\input links.tex
\input gfdl.tex

\end{document}