Commit 18c1a487 authored by Jim Fulton's avatar Jim Fulton

misstagged

parent 8cee87cb
See:
- the copyright notice in: COPYRIGHT.txt
- The Zope Public License in LICENSE.txt
Copyright (c) 2004 Zope Corporation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
FOR A PARTICULAR PURPOSE.
This source diff could not be displayed because it is too large. You can view the blob instead.
Zope Public License (ZPL) Version 2.1
-------------------------------------
A copyright notice accompanies this license document that
identifies the copyright holders.
This license has been certified as open source. It has also
been designated as GPL compatible by the Free Software
Foundation (FSF).
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the
following conditions are met:
1. Redistributions in source code must retain the
accompanying copyright notice, this list of conditions,
and the following disclaimer.
2. Redistributions in binary form must reproduce the accompanying
copyright notice, this list of conditions, and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
3. Names of the copyright holders must not be used to
endorse or promote products derived from this software
without prior written permission from the copyright
holders.
4. The right to distribute this software or to use it for
any purpose does not give you the right to use
Servicemarks (sm) or Trademarks (tm) of the copyright
holders. Use of them is covered by separate agreement
with the copyright holders.
5. If any files are modified, you must cause the modified
files to carry prominent notices stating that you changed
the files and the date of any change.
Disclaimer
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS''
AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL THE COPYRIGHT HOLDERS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
What's new on ZODB 3.7.1?
=========================
Packaging
---------
- (3.7.0b3) ZODB is now packaged without it's dependencies
ZODB no longer includes copies of dependencies such as
ZConfig, zope.interface and so on. It now treats these as
dependencies. If ZODB is installed with easy_install or
zc.buildout, the dependencies will be installed automatically.
- (3.7.0b3) ZODB is now a buildout
ZODB checkouts are now built and tested using zc.buildout.
ClientStorage
-------------
- (3.7.1) Fixed a serious bug that could cause client I/O to stop
(hang). This was accomonied by a critical log message along the
lines of: "RuntimeError: dictionary changed size during iteration".
- (3.7b4) Added logic to avoid spurious errors from the logging system
on exit.
- (3.7b2) Removed the "sync" mode for ClientStorage.
Previously, a ClientStorage could be in either "sync" mode or "async"
mode. Now there is just "async" mode. There is now a dedicicated
asyncore main loop dedicated to ZEO clients.
Applications no-longer need to run an asyncore main loop to cause
client storages to run in async mode. Even if an application runs an
asyncore main loop, it is independent of the loop used by client
storages.
This addresses a test failure on Mac OS X,
http://www.zope.org/Collectors/Zope3-dev/650, that I believe was due
to a bug in sync mode. Some asyncore-based code was being called from
multiple threads that didn't expect to be.
Converting to always-async mode revealed some bugs that weren't caught
before because the tests ran in sync mode. These problems could
explain some problems we've seen at times with clients taking a long
time to reconnect after a disconnect.
Added a partial heart beat to try to detect lost connections that
aren't otherwise caught,
http://mail.zope.org/pipermail/zodb-dev/2005-June/008951.html, by
perioidically writing to all connections during periods of inactivity.
Connection management
---------------------
- (3.7a1) When more than ``pool_size`` connections have been closed,
``DB`` forgets the excess (over ``pool_size``) connections closed first.
Python's cyclic garbage collection can take "a long time" to reclaim them
(and may in fact never reclaim them if application code keeps strong
references to them), but such forgotten connections can never be opened
again, so their caches are now cleared at the time ``DB`` forgets them.
Most applications won't notice a difference, but applications that open
many connections, and/or store many large objects in connection caches,
and/or store limited resources (such as RDB connections) in connection
caches may benefit.
- (3.7.0c1) Changed the automatic garbage collection when opening a connection
to only apply the garbage collections on those connections in the pool that
are closed. (This fixed issue 113932.)
DemoStorage
-----------
- (3.7a1) DemoStorage was unable to wrap base storages who did not have
an '_oid' attribute: most notably, ZEO.ClientStorage
(http://www.zope.org/Collectors/Zope/2016).
Documentation
-------------
- (3.7a1) Thanks to Stephan Richter for converting many of the doctest
files to ReST format. These are now chapters in the Zope 3 apidoc too.
IPersistent
-----------
- (3.7a1) The documentation for ``_p_oid`` now specifies the concrete
type of oids (in short, an oid is either None or a non-empty string).
Testing
-------
- (3.7b2) Fixed test-runner output truncation.
A bug was fixed in the test runner that caused result summaries to be
omitted when running on Windows.
Tools
-----
- (3.7b2) Fixed bug in 'fsrefs.py' which caused it to report erroneous
"missing" classes.
- (3.7a1) The changeover from zLOG to the logging module means that some
tools need to perform minimal logging configuration themselves. Changed
the zeoup script to do so and thus enable it to emit error messages.
BTrees
------
- (3.7a1) Suppressed warnings about signedness of characters when
compiling under GCC 4.0.x. See http://www.zope.org/Collectors/Zope/2027.
- (3.7a1) Support for 64-bit integer keys and values has been provided as a
compile-time option.
Connection
----------
- (3.7a1) An optimization for loading non-current data (MVCC) was
inadvertently disabled in ``_setstate()``; this has been repaired.
persistent
----------
- (3.7a1) Suppressed warnings about signedness of characters when
compiling under GCC 4.0.x. See http://www.zope.org/Collectors/Zope/2027.
- (3.7a1) PersistentMapping was inadvertently pickling volatile attributes
(http://www.zope.org/Collectors/Zope/2052).
After Commit hooks
------------------
- (3.7a1) Transaction objects have a new method,
``addAfterCommitHook(hook, *args, **kws)``. Hook functions
registered with a transaction are called after the transaction
commits or aborts. For example, one might want to launch non
transactional or asynchrnonous code after a successful, or aborted,
commit. See ``test_afterCommitHook()`` in
``transaction/tests/test_transaction.py`` for a tutorial doctest,
and the ``ITransaction`` interface for details.
ZODB 3.7
========
Introduction
------------
The ZODB package provides a set of tools for using the Zope Object
Database (ZODB) in Python programs separately from Zope. The tools
you get are identical to the ones provided in Zope, because they come
from the same source repository. They have been packaged for use in
non-Zope stand-alone Python applications.
The components you get with the ZODB release are as follows:
- Core ZODB, including the persistence machinery
- Standard storages such as FileStorage
- The persistent BTrees modules
- ZEO
- documentation
Our primary development platforms are Linux and Windows 2000. The
test suite should pass without error on all of these platforms,
although it can take a long time on Windows -- longer if you use
ZoneAlarm. Many particularly slow tests are skipped unless you pass
--all as an argument to test.py.
Compatibility
-------------
ZODB 3.7 requires Python 2.4 (2.4.2 or later). The BTree code may be
compiled with support for 64-bit keys and values for the "I" flavors;
older versions of the BTrees package will not be able to load
persistent BTrees that use 64-bit data (an exception will be raised on
load).
The Zope 2.10 release, and Zope 3.3 releases, should be compatible with this
version of ZODB. Note that Zope 2.7 and higher includes ZEO, so this package
should only be needed to run a ZEO server.
ZEO servers and clients are wholly compatible among 3.3, 3.4, 3.5, 3.6 and
3.7; a ZEO client from any of those versions can talk with a ZEO server from
any.
Trying to mix ZEO clients and servers from 3.3 or later from ZODB releases
before 3.3 is much harder. ZODB 3.3 introduced multiversion concurrency
control (MVCC), and earlier ZEO servers do not support MVCC: a 3.3+ ZEO
client cannot talk with an older ZEO server as a result.
In the other direction, a 3.3+ ZEO server can talk with older ZEO clients,
but because the names of some basic classes have changed, if any 3.3+ clients
commit modifications to the database it's likely that the database will
contain instances of classes that don't exist in (can't be loaded by) older
ZEO clients. For example, the database root object was an instance of
``ZODB.PersistentMapping.PersistentMapping`` before ZODB 3.3, but is an
instance of ``persistent.mapping.PersistentMapping`` in ZODB 3.3. A 3.3.1+
client can still load a ``ZODB.PersistentMapping.PersistentMapping`` object,
but this is just an alias for ``persistent.mapping.PersistentMapping``, and
an object of the latter type will be stored if a 3.3 client commits a change
to the root object. An older ZEO client cannot load the root object so
changed.
This limits migration possibilities: a 3.3+ ZEO server can be used with
older (pre-3.3) ZEO clients and serve an older database, so long as no 3.3+
ZEO clients commit changes to the database. The most practical upgrade path
is to bring up both servers and clients using 3.3+, not trying to mix pre-3.3
and post-3.3 ZEO clients and servers.
Prerequisites
-------------
You must have Python installed. If you've installed Python from RPM,
be sure that you've installed the development RPMs too, since ZODB
builds Python extensions. If you have the source release of ZODB,
you will need a C compiler.
You also need the ZConfig, zdaemon, zope.interface, zope.proxy and
zope.testing packages. If you are using easy_install or zc.buildout to
install ZODB, then these will be installed for you automatically.
Installation
------------
ZODB is released as a distutils package. The easiest ways to build
and install it are to use `easy_install
<http://peak.telecommunity.com/DevCenter/EasyInstall>`_, or
`zc.buildout <http://www.python.org/pypi/zc.buildout>`_.
To install by hand, first install the dependencies, ZConfig, zdaemon,
zope.interface, zope.proxy and zope.testing. These can be found
either in the `Python Package Index <http://www.python.org/pypi>`_,
or at http://download.zope.org/distribution/.
To build it, run the setup script::
% python setup.py build
The 64-bit support for the BTrees package may be enabled by using this
build command instead::
% python setup.py build_ext -DZODB_64BIT_INTS build
To test the build, run the test script::
% python test.py
For more verbose test output, append one or two '-v' arguments to this
command.
If all the tests succeeded, you can install ZODB using the setup
script::
% python setup.py install
This should now make all of ZODB accessible to your Python programs.
Testing for Developers
----------------------
The ZODB check outs are `buldouts <http://www.python.org/pypi/zc.buildout>`_.
When working from a ZODB checkout, first run the bootstrap.py script
to initialize the buildout:
% python bootstrap.py
and then use the buildout script to build ZODB and gather the dependencies:
% bin/buildout
This creates a test script:
% bin/test -v
This command will run all the tests, printing a single dot for each
test. When it finishes, it will print a test summary. The exact
number of tests can vary depending on platform and available
third-party libraries.::
Ran 1182 tests in 241.269s
OK
The test script has many more options. Use the ``-h`` or ``--help``
options to see a file list of options. The default test suite omits
several tests that depend on third-party software or that take a long
time to run. To run all the available tests use the ``--all`` option.
Running all the tests takes much longer.::
Ran 1561 tests in 1461.557s
OK
History
-------
The historical version numbering schemes for ZODB and ZEO are complicated.
Starting with ZODB 3.4, the ZODB and ZEO version numbers are the same.
In the ZODB 3.1 through 3.3 lines, the ZEO version number was "one smaller"
than the ZODB version number; e.g., ZODB 3.2.7 included ZEO 2.2.7. ZODB and
ZEO were distinct releases prior to ZODB 3.1, and had independent version
numbers.
Historically, ZODB was distributed as a part of the Zope application
server. Jim Fulton's paper at the Python conference in 2000 described
a version of ZODB he called ZODB 3, based on an earlier persistent
object system called BoboPOS. The earliest versions of ZODB 3 were
released with Zope 2.0.
Andrew Kuchling extracted ZODB from Zope 2.4.1 and packaged it for
use by standalone Python programs. He called this version
"StandaloneZODB". Andrew's guide to using ZODB is included in the Doc
directory. This version of ZODB was hosted at
http://sf.net/projects/zodb. It supported Python 1.5.2, and might
still be of interest to users of this very old Python version.
Zope Corporation released a version of ZODB called "StandaloneZODB
1.0" in Feb. 2002. This release was based on Andrew's packaging, but
built from the same CVS repository as Zope. It is roughly equivalent
to the ZODB in Zope 2.5.
Why not call the current release StandaloneZODB? The name
StandaloneZODB is a bit of a mouthful. The standalone part of the
name suggests that the Zope version is the real version and that this
is an afterthought, which isn't the case. So we're calling this
release "ZODB".
To make matters worse, we worked on a ZODB4 package for a while and
made a couple of alpha releases. We've now abandoned that effort,
because we didn't have the resources to pursue ot while also maintaining
ZODB(3).
License
-------
ZODB is distributed under the Zope Public License, an OSI-approved
open source license. Please see the LICENSE.txt file for terms and
conditions.
The ZODB/ZEO Programming Guide included in the documentation is a
modified version of Andrew Kuchling's original guide, provided under
the terms of the GNU Free Documentation License.
More information
----------------
We maintain a Wiki page about all things ZODB, including status on
future directions for ZODB. Please see
http://www.zope.org/Wikis/ZODB
and feel free to contribute your comments. There is a Mailman mailing
list in place to discuss all issues related to ZODB. You can send
questions to
zodb-dev@zope.org
or subscribe at
http://lists.zope.org/mailman/listinfo/zodb-dev
and view its archives at
http://lists.zope.org/pipermail/zodb-dev
Note that Zope Corp mailing lists have a subscriber-only posting policy.
Andrew's ZODB Programmers Guide is made available in several
forms, including DVI and HTML. To view it online, point your
browser at the file Doc/guide/zodb/index.html
Bugs and Patches
----------------
Bug reports and patches should be added to the Zope Collector, with
topic "Database":
http://collector.zope.org/Zope
..
Local Variables:
mode: indented-text
indent-tabs-mode: nil
sentence-end-double-space: t
fill-column: 70
End:
##############################################################################
#
# Copyright (c) 2006 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Bootstrap a buildout-based project
Simply run this script in a directory containing a buildout.cfg.
The script accepts buildout command-line options, so you can
use the -c option to specify an alternate configuration file.
$Id$
"""
import os, shutil, sys, tempfile, urllib2
tmpeggs = tempfile.mkdtemp()
ez = {}
exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
).read() in ez
ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
import pkg_resources
cmd = 'from setuptools.command.easy_install import main; main()'
if sys.platform == 'win32':
cmd = '"%s"' % cmd # work around spawn lamosity on windows
ws = pkg_resources.working_set
assert os.spawnle(
os.P_WAIT, sys.executable, sys.executable,
'-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
dict(os.environ,
PYTHONPATH=
ws.find(pkg_resources.Requirement.parse('setuptools')).location
),
) == 0
ws.add_entry(tmpeggs)
ws.require('zc.buildout')
import zc.buildout.buildout
zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
shutil.rmtree(tmpeggs)
[buildout]
develop = .
parts = test scripts
find-links = http://download.zope.org/distribution/
[test]
recipe = zc.recipe.testrunner
eggs = ZODB3
[scripts]
recipe = zc.recipe.egg
eggs = ZODB3
interpreter = py
Acknowledgments
---------------
This file lists people who have contribute code, bug fixes, ideas, and
other support for ZODB. Alas it is probably not complete.
Steve Alexander
Anthony Baxter
John Belmonte
Johan Dahlin
Toby Dickenson
Fred L. Drake, Jr.
Casey Duncan
Jon Dyte
Martijn Faassen
Jim Fulton
Marius Gedminas
Florent Guillaume
Shane Hathaway
Nicholas Henke
Jeremy Hylton
Matt Kromer
Andrew Kuchling
Andreas Jung
Brian Lloyd
Ken Manheimer
Dieter Maurer
Chris McDonough
Gintautas Miliauskas
Tim Peters
Chris Petrilli
Christian Reis
Guido van Rossum
Neil Schemenauer
Tres Seaver
Sidnei da Silva
Evan Simpson
Kapil Thangavelu
Jens Vagelpohl
Greg Ward
Barry Warsaw
Chris Withers
MKHOWTO=mkhowto
MKHTML=$(MKHOWTO) --html --iconserver=. --split=4 --dvips-safe
ZODBTEX = guide/gfdl.tex guide/introduction.tex guide/modules.tex \
guide/prog-zodb.tex guide/storages.tex guide/transactions.tex \
guide/zeo.tex guide/zodb.tex
default: pdf
all: pdf ps html
pdf: storage.pdf zodb.pdf
ps: storage.ps zodb.ps
html: storage/storage.html zodb/zodb.html
storage.pdf: storage.tex
$(MKHOWTO) --pdf $<
storage.ps: storage.tex
$(MKHOWTO) --ps $<
storage/storage.html: storage.tex
$(MKHTML) storage.tex
zodb.pdf: $(ZODBTEX)
$(MKHOWTO) --pdf guide/zodb.tex
zodb.ps: $(ZODBTEX)
$(MKHOWTO) --ps guide/zodb.tex
zodb/zodb.html: $(ZODBTEX)
$(MKHTML) guide/zodb.tex
clobber:
rm -rf storage.pdf storage.ps storage/ zodb.pdf zodb.ps zodb/
ZODB Documentation
==================
Simple text files
-----------------
This is a brief summary of the text documentation included with ZODB.
Most of the text actually uses the restructured text format. The
summary lists the title and path of each document.
BerkeleyDB Storages for ZODB
Doc/BDBStorage.txt
Using zdctl and zdrun to manage server processes
Doc/zdctl.txt
ZEO Client Cache
Doc/ZEO/cache.txt
Running a ZEO Server HOWTO
Doc/ZEO/howto.txt
ZEO Client Cache Tracing
Doc/ZEO/trace.txt
Formatted documents
-------------------
There are two documents written the Python documentation tools.
ZODB/ZEO Programming Guide
PDF: zodb.pdf
HTML: zodb/zodb.html
ZODB Storage API
PDF: storage.pdf
HTML: storage/storage.html
The documents located here can be formatted using the mkhowto script
which is part of the Python documentation tools. The recommended use
of this script is to create a symlink from some handy bin/ directory
to the script, located in Doc/tools/mkhowto in the Python source
distribution; that script will locate the various support files it
needs appropriately.
The Makefile contains the commands to produce both the PDF and HTML
versions of the documents.
ZEO Documentation
=================
This directory contains ZEO documentation.
howto.txt
The first place to look.
It provides a high-level overview of ZEO features and details on
how to install and configure clients and servers. Includes a
configuration reference.
cache.txt
Explains how the client cache works.
trace.txt
Describe cache trace tool used to determine ideal cache size.
ZopeREADME.txt
A somewhat dated description of how to integrate ZEO with Zope.
It provides a few hints that are not yet in howto.txt.
Zope Enterprise Objects (ZEO)
Installation
ZEO 2.0 requires Zope 2.4 or higher and Python 2.1 or higher.
If you use Python 2.1, we recommend the latest minor release
(2.1.3 as of this writing) because it includes a few bug fixes
that affect ZEO.
Put the package (the ZEO directory, without any wrapping directory
included in a distribution) in your Zope lib/python.
The setup.py script in the top-level ZEO directory can also be
used. Run "python setup.py install --home=ZOPE" where ZOPE is the
top-level Zope directory.
You can test ZEO before installing it with the test script::
python test.py -v
Run the script with the -h option for a full list of options. The
ZEO 2.0b2 release contains 122 unit tests on Unix.
Starting (and configuring) the ZEO Server
To start the storage server, go to your Zope install directory and
run::
python lib/python/ZEO/start.py -p port_number
This run the storage sever under zdaemon. zdaemon automatically
restarts programs that exit unexpectedly.
The server and the client don't have to be on the same machine.
If they are on the same machine, then you can use a Unix domain
socket::
python lib/python/ZEO/start.py -U filename
The start script provides a number of options not documented here.
See doc/start.txt for more information.
Running Zope as a ZEO client
To get Zope to use the server, create a custom_zodb module,
custom_zodb.py, in your Zope install directory, so that Zope uses a
ClientStorage::
from ZEO.ClientStorage import ClientStorage
Storage = ClientStorage(('', port_number))
You can specify a host name (rather than '') if you want. The port
number is, of course, the port number used to start the storage
server.
You can also give the name of a Unix domain socket file::
from ZEO.ClientStorage import ClientStorage
Storage = ClientStorage(filename)
There are a number of configuration options available for the
ClientStorage. See doc/ClientStorage.txt for details.
If you want a persistent client cache which retains cache contents
across ClientStorage restarts, you need to define the environment
variable, ZEO_CLIENT, or set the client keyword argument to the
constructor to a unique name for the client. This is needed so
that unique cache name files can be computed. Otherwise, the
client cache is stored in temporary files which are removed when
the ClientStorage shuts down. For example, to start two Zope
processes with unique caches, use something like::
python z2.py -P8700 ZEO_CLIENT=8700
python z2.py -P8800 ZEO_CLIENT=8800
Zope product installation
Normally, Zope updates the Zope database during startup to reflect
product changes or new products found. It makes no sense for
multiple ZEO clients to do the same installation. Further, if
different clients have different software installed, the correct
state of the database is ambiguous.
Zope will not modify the Zope database during product installation
if the environment variable ZEO_CLIENT is set.
Normally, Zope ZEO clients should be run with ZEO_CLIENT set so
that product initialization is not performed.
If you do install new Zope products, then you need to take a
special step to cause the new products to be properly registered
in the database. The easiest way to do this is to start Zope
once with the environment variable FORCE_PRODUCT_LOAD set.
The interaction between ZEO and Zope product installation is
unfortunate.
ZEO Client Cache
The client cache provides a disk based cache for each ZEO client. The
client cache allows reads to be done from local disk rather than by remote
access to the storage server.
The cache may be persistent or transient. If the cache is persistent, then
the cache file is retained for use after process restarts. A non-
persistent cache uses a temporary file.
The client cache is managed in a single file, of the specified size.
The life of the cache is as follows:
- The cache file is opened (if it already exists), or created and set to
the specified size.
- Cache records are written to the cache file, as transactions commit
locally, and as data are loaded from the server.
- Writes are to "the current file position". This is a pointer that
travels around the file, circularly. After a record is written, the
pointer advances to just beyond it. Objects starting at the current
file position are evicted, as needed, to make room for the next record
written.
A distinct index file is not created, although indexing structures are
maintained in memory while a ClientStorage is running. When a persistent
client cache file is reopened, these indexing structures are recreated
by analyzing the file contents.
Persistent cache files are created in the directory named in the ``var``
argument to the ClientStorage, or if ``var`` is None, in the current
working directory. Persistent cache files have names of the form::
client-storage.zec
where:
client -- the client name, as given by the ClientStorage's ``client``
argument
storage -- the storage name, as given by the ClientStorage's ``storage``
argument; this is typically a string denoting a small integer,
"1" by default
For example, the cache file for client '8881' and storage 'spam' is named
"8881-spam.zec".
This diff is collapsed.
ZEO Client Cache Tracing
========================
An important question for ZEO users is: how large should the ZEO
client cache be? ZEO 2 (as of ZEO 2.0b2) has a new feature that lets
you collect a trace of cache activity and tools to analyze this trace,
enabling you to make an informed decision about the cache size.
Don't confuse the ZEO client cache with the Zope object cache. The
ZEO client cache is only used when an object is not in the Zope object
cache; the ZEO client cache avoids roundtrips to the ZEO server.
Enabling Cache Tracing
----------------------
To enable cache tracing, you must use a persistent cache (specify a ``client``
name), and set the environment variable ZEO_CACHE_TRACE to a non-empty
value. The path to the trace file is derived from the path to the persistent
cache file by appending ".trace". If the file doesn't exist, ZEO will try to
create it. If the file does exist, it's opened for appending (previous trace
information is not overwritten). If there are problems with the file, a
warning message is logged. To start or stop tracing, the ZEO client process
(typically a Zope application server) must be restarted.
The trace file can grow pretty quickly; on a moderately loaded server, we
observed it growing by 7 MB per hour. The file consists of binary records,
each 34 bytes long if 8-byte oids are in use; a detailed description of the
record lay-out is given in stats.py. No sensitive data is logged: data
record sizes (but not data records), and binary object and transaction ids
are logged, but no object pickles, object types or names, user names,
transaction comments, access paths, or machine information (such as machine
name or IP address) are logged.
Analyzing a Cache Trace
-----------------------
The stats.py command-line tool is the first-line tool to analyze a cache
trace. Its default output consists of two parts: a one-line summary of
essential statistics for each segment of 15 minutes, interspersed with lines
indicating client restarts, followed by a more detailed summary of overall
statistics.
The most important statistic is the "hit rate", a percentage indicating how
many requests to load an object could be satisfied from the cache. Hit rates
around 70% are good. 90% is excellent. If you see a hit rate under 60% you
can probably improve the cache performance (and hence your Zope application
server's performance) by increasing the ZEO cache size. This is normally
configured using key ``cache_size`` in the ``zeoclient`` section of your
configuration file. The default cache size is 20 MB, which is small.
The stats.py tool shows its command line syntax when invoked without
arguments. The tracefile argument can be a gzipped file if it has a .gz
extension. It will be read from stdin (assuming uncompressed data) if the
tracefile argument is '-'.
Simulating Different Cache Sizes
--------------------------------
Based on a cache trace file, you can make a prediction of how well the cache
might do with a different cache size. The simul.py tool runs a simulation of
the ZEO client cache implementation based upon the events read from a trace
file. A new simulation is started each time the trace file records a client
restart event; if a trace file contains more than one restart event, a
separate line is printed for each simulation, and a line with overall
statistics is added at the end.
Example, assuming the trace file is in /tmp/cachetrace.log::
$ python simul.py -s 4 /tmp/cachetrace.log
CircularCacheSimulation, cache size 4,194,304 bytes
START TIME DURATION LOADS HITS INVALS WRITES HITRATE EVICTS INUSE
Jul 22 22:22 39:09 3218856 1429329 24046 41517 44.4% 40776 99.8
This shows that with a 4 MB cache size, the cache hit rate is 44.4%, the
percentage 1429329 (number of cache hits) is of 3218856 (number of load
requests). The cache simulated 40776 evictions, to make room for new object
states. At the end, 99.8% of the bytes reserved for the cache file were in
use to hold object state (the remaining 0.2% consists of "holes", bytes freed
by object eviction and not yet reused to hold another object's state).
Let's try this again with an 8 MB cache::
$ python simul.py -s 8 /tmp/cachetrace.log
CircularCacheSimulation, cache size 8,388,608 bytes
START TIME DURATION LOADS HITS INVALS WRITES HITRATE EVICTS INUSE
Jul 22 22:22 39:09 3218856 2182722 31315 41517 67.8% 40016 100.0
That's a huge improvement in hit rate, which isn't surprising since these are
very small cache sizes. The default cache size is 20 MB, which is still on
the small side::
$ python simul.py /tmp/cachetrace.log
CircularCacheSimulation, cache size 20,971,520 bytes
START TIME DURATION LOADS HITS INVALS WRITES HITRATE EVICTS INUSE
Jul 22 22:22 39:09 3218856 2982589 37922 41517 92.7% 37761 99.9
Again a very nice improvement in hit rate, and there's not a lot of room left
for improvement. Let's try 100 MB::
$ python simul.py -s 100 /tmp/cachetrace.log
CircularCacheSimulation, cache size 104,857,600 bytes
START TIME DURATION LOADS HITS INVALS WRITES HITRATE EVICTS INUSE
Jul 22 22:22 39:09 3218856 3218741 39572 41517 100.0% 22778 100.0
It's very unusual to see a hit rate so high. The application here frequently
modified a very large BTree, so given enough cache space to hold the entire
BTree it rarely needed to ask the ZEO server for data: this application
reused the same objects over and over.
More typical is that a substantial number of objects will be referenced only
once. Whenever an object turns out to be loaded only once, it's a pure loss
for the cache: the first (and only) load is a cache miss; storing the object
evicts other objects, possibly causing more cache misses; and the object is
never loaded again. If, for example, a third of the objects are loaded only
once, it's quite possible for the theoretical maximum hit rate to be 67%, no
matter how large the cache.
The simul.py script also contains code to simulate different cache
strategies. Since none of these are implemented, and only the default cache
strategy's code has been updated to be aware of MVCC, these are not further
documented here.
Simulation Limitations
----------------------
The cache simulation is an approximation, and actual hit rate may be higher
or lower than the simulated result. These are some factors that inhibit
exact simulation:
- The simulator doesn't try to emulate versions. If the trace file contains
loads and stores of objects in versions, the simulator treats them as if
they were loads and stores of non-version data.
- Each time a load of an object O in the trace file was a cache hit, but the
simulated cache has evicted O, the simulated cache has no way to repair its
knowledge about O. This is more frequent when simulating caches smaller
than the cache used to produce the trace file. When a real cache suffers a
cache miss, it asks the ZEO server for the needed information about O, and
saves O in the client cache. The simulated cache doesn't have a ZEO server
to ask, and O continues to be absent in the simulated cache. Further
requests for O will continue to be simulated cache misses, although in a
real cache they'll likely be cache hits. On the other hand, the
simulated cache doesn't need to evict any objects to make room for O, so it
may enjoy further cache hits on objects a real cache would have evicted.
This directory contains Andrew Kuchling's programmer's guide to ZODB
and ZEO. It was originally taken from Andrew's zodb.sf.net project on
SourceForge. Because the original version is no longer updated, this
version is best viewed as an independent fork now.
Write section on __setstate__
Continue working on it
Suppress the full GFDL text in the PDF/PS versions
% Administration
% Importing and exporting data
% Disaster recovery/avoidance
% Security
import sys, time, os, random
import transaction
from persistent import Persistent
from ZEO import ClientStorage
import ZODB
from ZODB.POSException import ConflictError
from BTrees import OOBTree
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 = OOBTree.OOBTree()
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
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'] = OOBTree.OOBTree()
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)
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 )
This diff is collapsed.
% Indexing Data
% BTrees
% Full-text indexing
%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). The latest version
of the guide is always available at
\url{http://www.zope.org/Wikis/ZODB/guide/index.html}.
\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, writing them out to disk when they are
modified, and dropping them from the cache if they haven't been used
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 actual 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 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{BDBFullStorage}), 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.
The author's ZODB project is described in a paper available here,
\url{http://www.amk.ca/python/writing/mx-architecture/}
This document will always be a work in progress. If you wish to
suggest clarifications or additional topics, please send your comments to
\email{zodb-dev@zope.org}.
\subsection{Acknowledgements}
Andrew Kuchling wrote the original version of this guide, which
provided some of the first ZODB documentation for Python programmers.
His initial version has been updated over time by Jeremy Hylton and
Tim Peters.
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, Thomas Guettler,
Chris McDonough, George Runyan.
% links.tex
% Collection of relevant links
\section{Resources}
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}
Persistent Programing with ZODB, by Jeremy Hylton and Barry Warsaw:
\\
Slides for a tutorial presented at the 10th Python conference. Covers
much of the same ground as this guide, with more details in some areas
and less in others.
\\
\url{http://www.zope.org/Members/bwarsaw/ipc10-slides}
This diff is collapsed.
This diff is collapsed.
% 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{BDBFullStorage}
\subsection{OracleStorage}
%Transactions and Versioning
% Committing and Aborting
% Subtransactions
% Undoing
% Versions
% Multithreaded ZODB Programs
\section{Transactions and Versioning}
\subsection{Committing and Aborting}
Changes made during a transaction don't appear in the database until
the transaction commits. This is done by calling the \method{commit()}
method of the current \class{Transaction} object, where the latter is
obtained from the \method{get()} method of the current transaction
manager. If the default thread transaction manager is being used, then
\code{transaction.commit()} suffices.
Similarly, a transaction can be explicitly aborted (all changes within
the transaction thrown away) by invoking the \method{abort()} method
of the current \class{Transaction} object, or simply
\code{transaction.abort()} if using the default thread transaction manager.
Prior to ZODB 3.3, if a commit failed (meaning the \code{commit()} call
raised an exception), the transaction was implicitly aborted and a new
transaction was implicitly started. This could be very surprising if the
exception was suppressed, and especially if the failing commit was one
in a sequence of subtransaction commits.
So, starting with ZODB 3.3, if a commit fails, all further attempts to
commit, join, or register with the transaction raise
\exception{ZODB.POSException.TransactionFailedError}. You must explicitly
start a new transaction then, either by calling the \method{abort()} method
of the current transaction, or by calling the \method{begin()} method of the
current transaction's transaction manager.
\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
transaction.commit(True)
# Abort a subtransaction
transaction.abort(True)
\end{verbatim}
A new subtransaction is automatically started upon successful 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}
transaction.get().setUser('amk')
transaction.get().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.
After you call \method{undo()} you must commit the transaction for the
undo to actually be applied.
\footnote{There are actually two different ways a storage can
implement the undo feature. Most of the storages that ship with ZODB
use the transactional form of undo described in the main text. Some
storages may use a non-transactional undo makes changes visible
immediately.} There is one glitch in the undo process. The thread
that calls undo may not see the changes to the object until it calls
\method{Connection.sync()} or commits another transaction.
\subsection{Versions}
\begin{notice}[warning]
Versions should be avoided. They're going to be deprecated,
replaced by better approaches to long-running transactions.
\end{notice}
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:
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.
This diff is collapsed.
\documentclass{howto}
\title{ZODB/ZEO Programming Guide}
\release{3.7.1}
\date{\today}
\author{A.M.\ Kuchling}
\authoraddress{\email{amk@amk.ca}}
\begin{document}
\maketitle
\tableofcontents
\copyright{Copyright 2002 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{modules}
\appendix
\input links.tex
\input gfdl.tex
\end{document}
This diff is collapsed.
This diff is collapsed.
# This file configures the logging module for the test harness:
# critical errors are logged to testing.log; everything else is
# ignored.
# Documentation for the file format is at
# http://www.red-dove.com/python_logging.html#config
[logger_root]
level=CRITICAL
handlers=normal
[handler_normal]
class=FileHandler
level=NOTSET
formatter=common
args=('testing.log', 'a')
filename=testing.log
mode=a
[formatter_common]
format=------
%(asctime)s %(levelname)s %(name)s %(message)s
datefmt=%Y-%m-%dT%H:%M:%S
[loggers]
keys=root
[handlers]
keys=normal
[formatters]
keys=common
#! /usr/bin/env python
"""Update version numbers and release dates for the next release.
usage: release.py version date
version should be a string like "3.2.0c1"
date should be a string like "23-Sep-2003"
The following files are updated:
- setup.py
- NEWS.txt
- doc/guide/zodb.tex
- src/ZEO/__init__.py
- src/ZEO/version.txt
- src/ZODB/__init__.py
"""
import fileinput
import os
import re
# In file filename, replace the first occurrence of regexp pat with
# string repl.
def replace(filename, pat, repl):
from sys import stderr as e # fileinput hijacks sys.stdout
foundone = False
for line in fileinput.input([filename], inplace=True, backup="~"):
if foundone:
print line,
else:
match = re.search(pat, line)
if match is not None:
foundone = True
new = re.sub(pat, repl, line)
print new,
print >> e, "In %s, replaced:" % filename
print >> e, " ", repr(line)
print >> e, " ", repr(new)
else:
print line,
if not foundone:
print >> e, "*" * 60, "Oops!"
print >> e, " Failed to find %r in %r" % (pat, filename)
# Nothing in our codebase cares about ZEO/version.txt. Jeremy said
# someone asked for it so that a shell script could read up the ZEO
# version easily.
# Before ZODB 3.4, the ZEO version was one smaller than the ZODB version;
# e.g., ZEO 2.2.7 shipped with ZODB 3.2.7. Now ZEO and ZODB share their
# version number.
def write_zeoversion(path, version):
f = open(path, "w")
print >> f, version
f.close()
def main(args):
version, date = args
replace("setup.py",
r'^VERSION = "\S+"$',
'VERSION = "%s"' % version)
replace("src/ZODB/__init__.py",
r'__version__ = "\S+"',
'__version__ = "%s"' % version)
replace("src/ZEO/__init__.py",
r'version = "\S+"',
'version = "%s"' % version)
write_zeoversion("src/ZEO/version.txt", version)
replace("NEWS.txt",
r"^Release date: .*",
"Release date: %s" % date)
replace("doc/guide/zodb.tex",
r"release{\S+}",
"release{%s}" % version)
if __name__ == "__main__":
import sys
main(sys.argv[1:])
##############################################################################
#
# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Zope Object Database: object database and persistence
The Zope Object Database provides an object-oriented database for
Python that provides a high-degree of transparency. Applications can
take advantage of object database features with few, if any, changes
to application logic. ZODB includes features such as a plugable storage
interface, rich transaction support, and undo.
"""
VERSION = "3.7.1"
# The (non-obvious!) choices for the Trove Development Status line:
# Development Status :: 5 - Production/Stable
# Development Status :: 4 - Beta
# Development Status :: 3 - Alpha
classifiers = """\
Development Status :: 5 - Production/Stable
Intended Audience :: Developers
License :: OSI Approved :: Zope Public License
Programming Language :: Python
Topic :: Database
Topic :: Software Development :: Libraries :: Python Modules
Operating System :: Microsoft :: Windows
Operating System :: Unix
"""
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
extra = dict(
scripts = ["src/ZODB/scripts/fsdump.py",
"src/ZODB/scripts/fsoids.py",
"src/ZODB/scripts/fsrefs.py",
"src/ZODB/scripts/fstail.py",
"src/ZODB/scripts/fstest.py",
"src/ZODB/scripts/repozo.py",
"src/ZEO/scripts/zeopack.py",
"src/ZEO/scripts/runzeo.py",
"src/ZEO/scripts/zeopasswd.py",
"src/ZEO/scripts/mkzeoinst.py",
"src/ZEO/scripts/zeoctl.py",
],
)
else:
entry_points = """
[console_scripts]
fsdump = ZODB.FileStorage.fsdump:main
fsoids = ZODB.scripts.fsoids:main
fsrefs = ZODB.scripts.fsrefs:main
fstail = ZODB.scripts.fstail:Main
repozo = ZODB.scripts.repozo:main
zeopack = ZEO.scripts.zeopack:main
runzeo = ZEO.runzeo:main
zeopasswd = ZEO.zeopasswd:main
mkzeoinst = ZEO.mkzeoinst:main
zeoctl = ZEO.zeoctl:main
"""
extra = dict(
install_requires = [
'zope.interface',
'zope.proxy',
'zope.testing',
'ZConfig',
'zdaemon',
],
zip_safe = False,
dependency_links = ['http://download.zope.org/distribution/'],
entry_points = entry_points,
)
scripts = []
import glob
import os
import sys
from distutils.extension import Extension
from distutils import dir_util
from distutils.dist import Distribution
from distutils.command.install_lib import install_lib
from distutils.command.build_py import build_py
from distutils.util import convert_path
if sys.version_info < (2, 4, 2):
print "This version of ZODB requires Python 2.4.2 or higher"
sys.exit(0)
# Include directories for C extensions
include = ['src']
# Set up dependencies for the BTrees package
base_btrees_depends = [
"src/BTrees/BTreeItemsTemplate.c",
"src/BTrees/BTreeModuleTemplate.c",
"src/BTrees/BTreeTemplate.c",
"src/BTrees/BucketTemplate.c",
"src/BTrees/MergeTemplate.c",
"src/BTrees/SetOpTemplate.c",
"src/BTrees/SetTemplate.c",
"src/BTrees/TreeSetTemplate.c",
"src/BTrees/sorters.c",
"src/persistent/cPersistence.h",
]
_flavors = {"O": "object", "I": "int", "F": "float"}
KEY_H = "src/BTrees/%skeymacros.h"
VALUE_H = "src/BTrees/%svaluemacros.h"
def BTreeExtension(flavor):
key = flavor[0]
value = flavor[1]
name = "BTrees._%sBTree" % flavor
sources = ["src/BTrees/_%sBTree.c" % flavor]
kwargs = {"include_dirs": include}
if flavor != "fs":
kwargs["depends"] = (base_btrees_depends + [KEY_H % _flavors[key],
VALUE_H % _flavors[value]])
if key != "O":
kwargs["define_macros"] = [('EXCLUDE_INTSET_SUPPORT', None)]
return Extension(name, sources, **kwargs)
exts = [BTreeExtension(flavor)
for flavor in ("OO", "IO", "OI", "II", "IF", "fs")]
cPersistence = Extension(name = 'persistent.cPersistence',
include_dirs = include,
sources= ['src/persistent/cPersistence.c',
'src/persistent/ring.c'],
depends = ['src/persistent/cPersistence.h',
'src/persistent/ring.h',
'src/persistent/ring.c']
)
cPickleCache = Extension(name = 'persistent.cPickleCache',
include_dirs = include,
sources= ['src/persistent/cPickleCache.c',
'src/persistent/ring.c'],
depends = ['src/persistent/cPersistence.h',
'src/persistent/ring.h',
'src/persistent/ring.c']
)
TimeStamp = Extension(name = 'persistent.TimeStamp',
include_dirs = include,
sources= ['src/persistent/TimeStamp.c']
)
winlock = Extension(name = 'ZODB.winlock',
include_dirs = include,
sources = ['src/ZODB/winlock.c']
)
exts += [cPersistence,
cPickleCache,
TimeStamp,
winlock,
]
# The ZODB.zodb4 code is not being packaged, because it is only
# need to convert early versions of Zope3 databases to ZODB3.
packages = ["BTrees", "BTrees.tests",
"ZEO", "ZEO.auth", "ZEO.zrpc", "ZEO.tests",
"ZODB", "ZODB.FileStorage", "ZODB.tests",
"persistent", "persistent.tests",
"transaction", "transaction.tests",
"ThreadedAsync",
"ZopeUndo", "ZopeUndo.tests",
]
def copy_other_files(cmd, outputbase):
# A delicate dance to copy files with certain extensions
# into a package just like .py files.
extensions = ["*.conf", "*.xml", "*.txt", "*.sh", "*.txt"]
directories = [
"transaction",
"transaction/tests",
"persistent/tests",
"ZEO",
"ZODB",
"ZODB/tests",
]
for dir in directories:
exts = extensions
dir = convert_path(dir)
inputdir = os.path.join("src", dir)
outputdir = os.path.join(outputbase, dir)
if not os.path.exists(outputdir):
dir_util.mkpath(outputdir)
for pattern in exts:
for fn in glob.glob(os.path.join(inputdir, pattern)):
# glob is going to give us a path including "src",
# which must be stripped to get the destination dir
dest = os.path.join(outputbase, fn[4:])
cmd.copy_file(fn, dest)
class MyLibInstaller(install_lib):
"""Custom library installer, used to put hosttab in the right place."""
# We use the install_lib command since we need to put hosttab
# inside the library directory. This is where we already have the
# real information about where to install it after the library
# location has been set by any relevant distutils command line
# options.
def run(self):
install_lib.run(self)
copy_other_files(self, self.install_dir)
class MyPyBuilder(build_py):
def build_packages(self):
build_py.build_packages(self)
copy_other_files(self, self.build_lib)
class MyDistribution(Distribution):
# To control the selection of MyLibInstaller and MyPyBuilder, we
# have to set it into the cmdclass instance variable, set in
# Distribution.__init__().
def __init__(self, *attrs):
Distribution.__init__(self, *attrs)
self.cmdclass['build_py'] = MyPyBuilder
self.cmdclass['install_lib'] = MyLibInstaller
doclines = __doc__.split("\n")
setup(name="ZODB3",
version=VERSION,
maintainer="Zope Corporation",
maintainer_email="zodb-dev@zope.org",
packages = packages,
package_dir = {'': 'src'},
ext_modules = exts,
headers = ['src/persistent/cPersistence.h',
'src/persistent/ring.h'],
license = "ZPL 2.1",
platforms = ["any"],
description = doclines[0],
classifiers = filter(None, classifiers.split("\n")),
long_description = "\n".join(doclines[2:]),
distclass = MyDistribution,
**extra)
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
persistent
transaction
zope.interface
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
# hack to overcome dynamic-linking headache.
from _IFBTree import *
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
# hack to overcome dynamic-linking headache.
from _IIBTree import *
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
# hack to overcome dynamic-linking headache.
from _IOBTree import *
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
import persistent
class Length(persistent.Persistent):
"""BTree lengths are too expensive to compute
Objects that use BTrees need to keep track of lengths themselves.
This class provides an object for doing this.
As a bonus, the object support application-level conflict
resolution.
It is tempting to to assign length objects to __len__ attributes
to provide instance-specific __len__ methods. However, this no
longer works as expected, because new-style classes cache
class-defined slot methods (like __len__) in C type slots. Thus,
instance-define slot fillers are ignores.
"""
def __init__(self, v=0):
self.value = v
def __getstate__(self):
return self.value
def __setstate__(self, v):
self.value = v
def set(self, v):
self.value = v
def _p_resolveConflict(self, old, s1, s2):
return s1 + s2 - old
def _p_independent(self):
# My state doesn't depend on or materially effect the state of
# other objects.
return 1
def change(self, delta):
self.value += delta
def __call__(self, *args):
return self.value
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
# hack to overcome dynamic-linking headache.
from _OIBTree import *
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
# hack to overcome dynamic-linking headache.
from _OOBTree import *
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/*############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
############################################################################*/
#define MASTER_ID "$Id$\n"
/* IFBTree - int key, float value BTree
Implements a collection using int type keys
and float type values
*/
/* Setup template macros */
#define PERSISTENT
#define MOD_NAME_PREFIX "IF"
#define INITMODULE init_IFBTree
#define DEFAULT_MAX_BUCKET_SIZE 120
#define DEFAULT_MAX_BTREE_SIZE 500
#include "intkeymacros.h"
#include "floatvaluemacros.h"
#include "BTreeModuleTemplate.c"
/*############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
############################################################################*/
#define MASTER_ID "$Id$\n"
/* IIBTree - int key, int value BTree
Implements a collection using int type keys
and int type values
*/
/* Setup template macros */
#define PERSISTENT
#define MOD_NAME_PREFIX "II"
#define INITMODULE init_IIBTree
#define DEFAULT_MAX_BUCKET_SIZE 120
#define DEFAULT_MAX_BTREE_SIZE 500
#include "intkeymacros.h"
#include "intvaluemacros.h"
#include "BTreeModuleTemplate.c"
/*############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
############################################################################*/
#define MASTER_ID "$Id$\n"
/* IOBTree - int key, object value BTree
Implements a collection using int type keys
and object type values
*/
#define PERSISTENT
#define MOD_NAME_PREFIX "IO"
#define DEFAULT_MAX_BUCKET_SIZE 60
#define DEFAULT_MAX_BTREE_SIZE 500
#define INITMODULE init_IOBTree
#include "intkeymacros.h"
#include "objectvaluemacros.h"
#include "BTreeModuleTemplate.c"
This diff is collapsed.
This diff is collapsed.
# This is a Python package.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
#define KEYMACROS_H "$Id$\n"
#define KEY_TYPE PyObject *
#define KEY_TYPE_IS_PYOBJECT
#define TEST_KEY_SET_OR(V, KEY, TARGET) if ( ( (V) = PyObject_Compare((KEY),(TARGET)) ), PyErr_Occurred() )
#define INCREF_KEY(k) Py_INCREF(k)
#define DECREF_KEY(KEY) Py_DECREF(KEY)
#define COPY_KEY(KEY, E) KEY=(E)
#define COPY_KEY_TO_OBJECT(O, K) O=(K); Py_INCREF(O)
#define COPY_KEY_FROM_ARG(TARGET, ARG, S) TARGET=(ARG)
This diff is collapsed.
This diff is collapsed.
# If tests is a package, debugging is a bit easier.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
BTrees
ThreadedAsync
ZConfig
ZODB
persistent
transaction
zdaemon
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment