############################################################################ # # Copyright: This text is freely distributable and can be changed in any manner # whatsoever. # No rights reserved, but all liabilities and warranties excluded. # # Harm Kirchhoff, April 26, 2004 # # To find out more about ZODB or Zope, visit: www.Zope.org. # # The following is a number of hands-on code example how to use the ZODB, # which forms a part of Zope. # # When I first looked at Zope I was looking for a database. # Zope caught my eye, because it offered much more that a database, so # why not doing two things in one: Learning the web and having a database # and on top of it, all in python plus the possibility to expand my programs # with a web-based surface without having to learn everything all over again. # # However, there was little code available for absolute dumbo-newbies that # would show you the very first steps for the ZODB, allthough there are # plenty of good books about ZOPE. I had to spent days figuring out the # most basic steps (I actually bought most of the ZODB books in print). # As experience has shown, it is not important to have a good product, but it # is more important to document and market the product you have well. # Unfortunately, ZOPE and ZODB are changing fast and the little sources # available to ZODB are often out of date. # # What follows is a very very simple, hands-on intorduction of the first # and basic steps that will allow the reader to use the ZODB in order to # construct powerful database applications. # # The ZODB is a excellent and easy to use product, which essentially simulates # a giant python dictionary (to put is simple). It integrates fully into # your python code and should be first choice for persons who are looking for # a cheap and reliable, yet powerful and professional data base to be # programmed in python. I am using the ZODB for all kinds of small tasks, # one of which is for example to store thousands of sales records received in excel # files. Using python and zope I can resort, and analyze such data # much quicker than with with functions that marketed accounting software # packages or spread sheet applications provide. # # The following is a collection of very simple, tested code examples using # the ZODB. They all have been tested under windows 2000, Zope 2.6.2 and python 2.3. # I deliberately refrained from writing a manual, because I felt that a small # library of hands-on examples which are ready to use, would be more instructive # for programmers who try their first steps with ZODB. # # Before you start running any of the examples, please modify the PATH # variable at the start of the code, right after the imports into a path that # is valid for reading and writing for you on your system. # You must also ensure that Python knows where to find your ZODB files. # If Python can not find the ZODB module, you will have to include the path # of the folder with the ZODB code files in Python's PATH variable. # # I recommend to read through (but not try to understand it all) the following # article to get an idea of the ZODB context: # http://www.zope.org/Members/jim/Info/IPC8/ZODB3/index.html # http://www.informit.com/articles/article.asp?p=23413 # http://zope.org/Documentation/Articles/ZODB1 # http://zope.org/Wikis/ZODB/FrontPage/guide/index.html # Do not try to understand everything that is written, just read on if you do not # understand something. # The articles will create a background knowledge, that will help you to # understand better some of the terms and code used hereafter. Keep these # articles with you for future reference, as you progress, you will understand # more and more of the information they contain. # # The following text assumes that you have a working knowledge of Python and # that you know the basics of python dictionaries. You should be on a level to # have read and understand most of 'Learning Python' by Mark Lutz and David Ascher. # I deliberately kept this module free of any complications, so you will not # have to deal with classes or other complicated constructs for most of this # module. # # I would also like to thank the following persons for their efforts to # make comments and suggestions on this module: # Thomas Guettler # # In case you have questions: hk@web.de # ############################################################################ # **************************************************************************** # Chapter 1: # Open, Write and Close the ZODB # # **************************************************************************** import time # These standard modules are imported for cosmetics, import pprint # they are not necessary to operate the ZODB. # ******** Zope imports ************ import ZODB from ZODB import FileStorage, DB PATH = 'C:/temp/test.fs' # tells ZODB where to put the data files def test0(): "A first connection to a ZODB" """ This mini-session opens a ZODB, writes a small dictionary to it, closes the ZODB, then re-opens it, retrieves and prints its contents and closes it again. Before running this code, please set the PATH variable to an empty, best of all, newly created directory on your system. After you ran test0() you will find the files, that constitute your ZODB stored under PATH. You can back up these files in any customary manner. If you ever encounter a crash, you can simply re-construct your data base by copying back files which you had previously backed up. You will not need to perform any additional registrations. ZODB will simply open the re-stored files and work with it as before. (However, if you use a CR Writer, you may have to mark the files are writeable after you copied them back onto your system, because CDs are non-writeable devices.) """ storage = FileStorage.FileStorage(PATH) db = DB(storage) connection = db.open() dbroot = connection.root() dbroot['testme'] = {'i love tests' : [1,2,3]} get_transaction().commit() print 'Here it is:' print dbroot.keys() pprint.pprint(dbroot) connection.close() db.close() storage.close() storage = FileStorage.FileStorage(PATH) db = DB(storage) connection = db.open() dbroot = connection.root() print '... and here it is again !' pprint.pprint(dbroot) connection.close() db.close() storage.close() return """ Consider the following lines of code: dbroot['testme'] = {'i love tests' : [1,2,3]} Demonstrates the use of a ZODB: It is used like a python dictionary. Note that there is an important difference between: dbroot['testme'] = {...} and dbroot = { 'testme' : {} } the print out in both cases is the same, but the later line replaces the entire root of your database, which is not recommendable. When you work with the ZODB, store your data in sub-branches of the root-object. (Root object is the instance of the classes, that is returned by: connection.root(), in the example below). After you ran this example, you can go to the file path specified in the line storage = FileStorage.FileStorage() You will find newly created files, which you can open with a normal text editor. They will look somewhat strange, but you can see your data somewhere. The FileStorage is the native ZODB storage system. There are plug-in to use other types of databases, but this is beyond this explanation. """ def copy_sales(): "copies contents of Sales.pkl into ZODB." """ Originally, before I felt the need to switch to ZODB, I used the pickle module of python to store all my data. As data accumulated, I had to migrate to ZODB. Bleow is the code I used to copy my pickled dictioaries into the ZODB root object. """ import pickle # Read all data from pkl into a dictionary f=open('C:\Documents and Settings\Kirchhoff\My Documents\SALES\SalesDB.pkl','r') Sales=pickle.load(f) f.close() # Now Sales contains the dictionary, that was stored in the pickle file # Open ZODB storage = FileStorage.FileStorage(PATH) db = DB(storage) connection = db.open() dbroot = connection.root() # copy dales into root dbroot['sales'] = Sales # note that this would not work if you would copy: # dbroot = Sales get_transaction().commit() # and now let's see if it is there (same code as above) # close the connection connection.close() db.close() storage.close() return """ The code above reveals that you will need four instances of classes in order to work with ZODB. We call each an object, and the objects are as follows: storage = FileStorage.FileStorage(PATH) # the storage object db = DB(storage) # the data base object connection = db.open() # the connection object dbroot = connection.root() # and the root object (mentioned above) get_transaction().commit() is the command, which makes the changes permanent. if no get_transaction().commit() is issued, the changes will not be saved in the database. This has some important consequences: 1. Any change you make to the root object and its sub-branches are not permanent, until you issue a get_transaction().commit() 2. If your program aborts due to an error, before the get_transaction().commit() was issued, no change will be saved and you do not need to recover your database as it remains in the state as it was. Note that get_transaction() returns a transaction object, which describes a single transaction in the ZODB. This object knows 4 methods: begin() ; not necessary commit(); save changes, this is what we used above. abort() ; abort all changes note() ; add a note to the log, see later. There is no need to begin() a transaction. I recommend not to use begin(), because it will erease all changes made to the root object when it is called, just as abort() does. Therefore, you must call begin at the very beginning of your code, but you do not need to call it at all. It is possible to issue a t = get_transaction() statement and to perform the actions on this transaction t. This way, several transactions could be handled in parallel. Since in the above code, we are only using one transaction at a time, it is possible to perfrom the commit() on a new transaction, i.e. every commit() will automatically form a separate transaction. This module will not deal with multi-transaction processing. For more information, refer to: http://www.zope.org/Members/mcdonc/HowTos/transaction """ def show_contents(): "Shows contents of DB copied with copy_sales()." # Be careful, if you copied a lot of data this may work slowly. # Instead of print dbroot, you may want to use the pprint module. storage = FileStorage.FileStorage(PATH) db = DB(storage) connection = db.open() dbroot = connection.root() print dbroot connection.close() db.close() storage.close() return """ From the experience gained so far, it is possible to built two functions: 1. open_zodb() 2. close_zodb() open_zodb returns a tuple containing all objects which describe the database, close_zodb closes the database, taking as its argument a descriptor returned by open_zodb(). Having two such functions is usually useful, since it makes code more readable. From the shell, the database can be opened and closed: >>> import ZODB_Tutorial >>> s=ZODB_Tutorial.open_zodb() >>> print s[0]['sales'].keys() ['SA=HMI'] >>> ZODB_Tutorial.close(s) Of course, the output depends on the keys, which are used in the database. """ def open_zodb(Path=PATH): "Open ZODB." """ Returns a tuple consisting of:(root,connection,db,storage) The same tuple must be passed to close_zodb() in order to close the DB. """ # Connect to DB storage = FileStorage.FileStorage(Path) db = DB(storage) connection = db.open() root = connection.root() return (root,connection,db,storage) def close_zodb(DataBase): "Closes the ZODB." """ This function MUST be called at the end of each program !!! See open_zodb() for a description of the argument. """ get_transaction().abort() DataBase[1].close() DataBase[2].close() DataBase[3].close() return True """ If you erease your data base identified by the PATH variable, a sample session with the above functions should look like this: >>> import ZODB_Tutorial >>> print ZODB_Tutorial.PATH C:/temp/test.fs >>> descr = open_zodb(PATH) >>> descr[0]['key1'] = 'contents of key1' >>> get_transaction().commit() >>> close_zodb(descr) True >>> descr = open_zodb(PATH) >>> print descr[0] {'key1': 'contents of key1'} >>> close_zodb(descr) True >>> Storing data in ZODB is simple, if you know the tricks. Create a first database, or a new branch in an existing root object >>> descr = open_zodb(PATH) >>> descr[0]['key1'] = 'contents of key1' >>> get_transaction().commit() >>> close_zodb(descr) You will find, that this transaction was successful, because your change included the top level of the root object, just as in the above example copy_sales(): Now, see what happens, if you do not change the top-level object: >>> descr = open_zodb(PATH) >>> print descr[0] {'key1': 'contents of key1'} >>> close_zodb(descr) True >>> descr = open_zodb(PATH) >>> descr[0]['key1'] = { 'key2' : 'sub-branch'} >>> print descr[0] {'key1': {'key2': 'sub-branch'}} >>> get_transaction().commit() >>> close_zodb(descr) True >>> descr = open_zodb(PATH) >>> print descr[0] {'key1': {'key2': 'sub-branch'}} Up to here, all worked fine, because you changed 'key1', the top level of the root object (contained in descr[0]). Now proceed, but change 'key2': >>> descr[0]['key1']['key2'] = 'changed' >>> print descr[0] {'key1': {'key2': 'changed'}} >>> get_transaction().commit() >>> close_zodb(descr) True >>> descr = open_zodb(PATH) >>> print descr[0] {'key1': {'key2': 'sub-branch'}} >>> As you can see, nothing changed, or more precisely, the change which you made to your root object's sub-branch was not persistent. This is because your change did not affect the first-level sub-branch ['key1'], but the second-level sub-sub-branch ['key2']. ZODB can not see beyond the top level object, so it does not know what changed where automatically. You have to signal the change in the root object, using the _p_changed flag: >>> descr[0]['key1']['key2'] = 'changed' >>> descr[0]._p_changed = 1 >>> get_transaction().commit() >>> close_zodb(descr) True >>> descr = open_zodb(PATH) >>> print descr[0] {'key1': {'key2': 'changed'}} >>> You can see that the _p_changed flag belongs to the root object only. If you try to set it for a sub branch, such as: descr[0]['key1']['key2']._p_changed = 1 you will get an error. """ # **************************************************************************** # Chapter 2: # Opening, a little more sophisticated # **************************************************************************** # (This chapter is based on the tutorial 'How To Love ZODB and Forget RDBMS' # by Duncan McGreggor and Casey Duncan.) # It is possible to define the database to be opened in a file. # With a text editor, create the following ASCII file: """ path C:/temp/test.fs """ # and name it 'test.conf' and save it in /C:/temp/ import ZODB.config def open_zodb_file(): db = ZODB.config.databaseFromURL('C:/temp/test.conf') connection = db.open() root = connection.root() pprint.pprint(root) connection.close() db.close() return True # Calling this function will produce the output: # >>> import ZODB_Tutorial # >>> ZODB_Tutorial.open_zodb_file() # {'key3': 'Testing new save', 'key2': 'changed again', 'key1': {'key2': 'changed'}, # 'sales': {'SA=HMI': {}}, 'key1a': {'key1a2': ' contents of key1a2'}, 'forecast': {}} # True # # **************************************************************************** # Chapter 3: # Running applications using the ZODB: # **************************************************************************** """ Use try: and finally: after you opened the database. This will save you many hours of useless work: Whenever you have an error after you connected to ZODB and python aborts without closing the connection, you will have to restart python again under windows, because otherwise you will get a winlock error. To avoid this, use the try statement immediately after connecting to the ZODB: """ def use_try(): "Use try and finally." data_base = open_zodb(PATH) if data_base == False : close_zodb(data_base) ; return False # ---> try: # put your code in here. # if you have an error, ZODB is closed correctly # (provided you did not manipulate the data_base descriptor) # and you can debug under windows without having to # re-start python. s = [] print s[3] # forces an error finally: close_zodb(data_base) return # **************************************************************************** # Chapter 4: # Saving: More Sophisticated # # **************************************************************************** """ When storing data, keep in mind, that ZODB only makes the changes permanent, once you commit. I recommend to commit only at the very end of your transaction, when you are sure that the transaction completed successfully, but it is possible to commit at any stage. When committing changes, ZODB offers the possibility to add comments. These comments will be stored in the change log. This change log can be used later to undo transactions. I use a central routine to handle all committing of data. Setting _p_changed at any stage is necessary, because 99.9% of the changes concern sub-level objects. """ def save_zodb(DataBase, Msg, User ='hk'): "Makes all changes permanent." """ Data_Base, is the normal database descriptor as returned by open_zodb(). """ DataBase[0]._p_changed = 1 get_transaction().note(Msg) get_transaction().setUser(User) ctr = 0 while ctr < 3: try: get_transaction().commit() # normal case. break except ConflictError: # in case there are several time.sleep( 1 ) # commits at the same time ctr += 1 # wait a second and try again. continue except KeyError: get_transaction().abort() print 'KeyError' return False except: get_transaction().abort() # clean up before raising errors raise if ctr > 0: return False return True """ This saving function makes every call a single and separate transaction. Before testing this function, some explanations about the logging of transactions in the ZODB. In ZODB each transaction is recorded and appended to the database. It is therefore possible to undo such transactions (in theory, in practice restrictions apply). In order to minimize storage, it is possbile to pack the database, but in that case, all transactions will be fully integrated and can not be undone. Lets use the above save_zodb() and have a look at our UndoLog: >>> descr = open_zodb(PATH) >>> print descr[0] 'key1': {'key2': 'changed'}} >>> descr[0] ['key1a'] = {'key1a2' : ' contents of key1a2'} >>> save_zodb(descr,'test the log') True >>> close_zodb(descr) True >>> descr = open_zodb(PATH) >>> print descr[0] {'key1': {'key2': 'changed'}, 'key1a': {'key1a2': ' contents of key1a2'}} >>> descr[3].undoLog(0, 20) [ { 'description': 'test the log', 'user_name' : '/ hk', 'id' : 'A1Md4YN6Mog=', 'time' : 1076999130.8150001 }, { 'description': '' 'user_name' : '', 'id' : 'A1Md2WvvEd0=', 'time' : 1076998645.2969999 }, { 'description': '', 'user_name' : '', 'id' : 'A1Md0aOQCu4=', 'time' : 1076998178.335 }, { 'description': '', 'user_name' : '', 'id' : 'A1MdzaZ9VmY=', 'time' : 1076997939.0209999 }, { 'description': 'initial database creation', 'user_name' : '', 'id' : 'A1Mdy8U+ZVU=', 'time' : 1076997826.2290001 } ] >>> I resorted the print out of the undo log, to make it easier to read. As you can see, it is a list of dictionaries, identifying each transactions we did. Only two transactions are described. The very first, ZODB automatically inserted: 'initial database creation' and the most recent one:'test the log' ZODB only makes changes permanent once you commit them, using get_transactin().commit(). I use this feature, to commit only at the end of my code, once I can be sure that the entire code was executed without error. If you work like that, the amount of transactions stays manageable, and commenting each transaction becomes a valuable practice, if you are trying to undo changes, or trace errors. It is recomendable to add a description for any transaction that is done, and to make it as accurate as possible, using the data available. For example, if you register a new account, a description 'acc reg' is less expressive, than 'acc no.' + str(accno) +' reg for ' + acc_owner where accno is a variable containing the account number and acc_owner a variable containing the account owner. in other words, if possible make a custom made description for every transaction, using the variables at hand. """ # **************************************************************************** # Chapter 5: # The UndoLog and undoing transactions # # **************************************************************************** """ Note that the undolog is not provided by the root object, which is used to write the data (data_base[0]), nor by the storage object, but by the database object. (data_base[3] when you use data_base = ZODB_Tutorial.open_zodb('C:/temp/test_zodb.fs') The log can be made more readable, by using a routine, such as undo_list() that provides some formating: """ def undo_list(data_base): "List of undo log, with latest transaction first." """ The time is converted into a readable time. """ ret = [ ['Date','Trans. no.','User','Description'] ] undolog = data_base[3].undoLog(0, 20) # convert the time stamp into something readable for transact in undolog: transact['time'] = time.ctime(transact['time']) # convert into a list of lists for transact in undolog: id = transact['id'] usr = transact['user_name'] tme = transact['time'] des = transact['description'] ret.append([tme,id,usr,des]) return ret """ Let us delete the files created by ZODB under PATH and make a new file: First delte the files manually, and then: # the usual: >>> import ZODB_Tutorial >>> data_base = ZODB_Tutorial.open_zodb(PATH) >>> pprint.pprint( data_base[0] ) {} # we are not astonished, since we deleted our database completely >>> data_base[0] ['main'] = {} >>> ZODB_Tutorial.save_zodb(data_base,'created main-branch','me') >>> data_base[0] ['main'] ['sub-branch 1'] = 'sub level 1' >>> ZODB_Tutorial.save_zodb(data_base,'sub-branch 1','you') True >>> data_base[0] ['main'] ['sub-branch 2'] = 'sub level 1, 2nd branch' >>> ZODB_Tutorial.save_zodb(data_base,'sub-branch 2','him') True # now a look at our undo log: >>> lst = undo_list(data_base) >>> for l in lst: print l ['Date', 'Trans. no.', 'User', 'Description'] ['Mon Apr 19 13:21:05 2004', 'A1SFZRcjXLs=', '/ him', 'sub-branch 2'] ['Mon Apr 19 13:20:26 2004', 'A1SFZHJsaFU=', '/ you', 'sub-branch 1'] ['Mon Apr 19 13:17:54 2004', 'A1SFYedJl2Y=', '/ me', 'created main-branch'] ['Mon Apr 19 13:16:34 2004', 'A1SFYJEUV+4=', '', 'initial database creation'] >>> # Now let's undo this bit by bit, by cutting and pasting the Transaction numbers. # The trick is, that you must commit and undo, just like any other transaction: >>> data_base[2].undo('A1SFZRcjXLs=') >>> pprint.pprint( data_base[0] ) {'main': {'sub-branch 2': 'sub level 1, 2nd branch', 'sub-branch 1': 'sub level 1'}} >>> get_transaction().commit() >>> pprint.pprint( data_base[0] ) {'main': {'sub-branch 2': 'sub level 1, 2nd branch', 'sub-branch 1': 'sub level 1'}} >>> lst = undo_list(data_base) >>> for l in lst: print l ['Date', 'Trans. no.', 'User', 'Description'] ['Mon Apr 19 13:24:42 2004', 'A1SFaLXUCUQ=', '', ''] ['Mon Apr 19 13:21:05 2004', 'A1SFZRcjXLs=', '/ him', 'sub-branch 2'] ['Mon Apr 19 13:20:26 2004', 'A1SFZHJsaFU=', '/ you', 'sub-branch 1'] ['Mon Apr 19 13:17:54 2004', 'A1SFYedJl2Y=', '/ me', 'created main-branch'] ['Mon Apr 19 13:16:34 2004', 'A1SFYJEUV+4=', '', 'initial database creation'] >>> # The result should not be astonishing, except for the fact that an additional # transaction appears: 'A1SFaLXUCUQ=' # This is the undo() transaction and as any other transaction, it can be undone: >>> ZODB_Tutorial.close_zodb(data_base) True >>> data_base = ZODB_Tutorial.open_zodb(PATH) >>> pprint.pprint( data_base[0] ) {'main': {'sub-branch 1': 'sub level 1'}} >>> data_base[2].undo('A1SFaLXUCUQ=') >>> get_transaction().commit() >>> pprint.pprint( data_base[0] ) {'main': {'sub-branch 1': 'sub level 1'}} >>> ZODB_Tutorial.close_zodb(data_base) True >>> data_base = ZODB_Tutorial.open_zodb(PATH) >>> pprint.pprint( data_base[0] ) {'main': {'sub-branch 2': 'sub level 1, 2nd branch', 'sub-branch 1': 'sub level 1'}} >>> Please notice that you will not see the result of the undo(), unless you close and re-open your database. A common source of errors as a beginner will be that the undolog and undo() work on different instances: undolog = data_base[3].undoLog(0, 20) , retrives the most recent 20 log entries, data_base[2].undo('A1SFaLXUCUQ=') undoes a transaction. undo() works on the database object, while undoLog() works on the storage object ! Closely related to undo() is the packing of the database. When you pack your database, you will re-claim some storage, because the changes will be integrated and the space related to the transaction regsitration will be freed. As a result, you will not be able to undo any transactions and your undolog will be empty: >>> data_base[2].pack() >>> lst = ZODB_Tutorial.undo_list(data_base) >>> for l in lst: print l ['Date', 'Trans. no.', 'User', 'Description'] >>> You can specify the number of days that should remain un-packed, by giving the .pack() call an argument: .pack(5) will pack all transactions that are older than the last five days, but you will still be able to undo transactions that were commited during the past 5 days. """ # **************************************************************************** # Chapter 6: # ZODB one important restriction # # **************************************************************************** """ Now you have learned all that is necessary to use the ZODB as a giant python dictionary. When doing so, do not forget one important thing: ALL KEYS USED, MUST BE OF THE SAME TYPE. In other words, all your branches and sub-branches should use the same type of key. (either characters, integers or other) In my experience it is recommendable to use characters, since they allow most variety and you can easily convert any number into characters, either by using their ascii codes or by converting them into strings. """ # **************************************************************************** # Chapter 7: # ZODB is not only a dictionary # # **************************************************************************** """ All of the above examples were limited to the use of ZODB as a persistent python dictionary. However, the ZODB can store any 'pickable' python object. In principle that means that any standard python object, such as dictionaries, lists, classes and mixtures thereof can be stored. If you come from an non-object oriented language, classes will probably be something strange to you and you will take some time to get to use them (once you are, they are a really cool thing, but you can live without them, at least for now.) To explain classes simply, imagine them as a container for variables, that you wish to order by topic. Instead of polluting your namespace with hundreds of variables, you order them into a couple of classes, such as: class employee: first_name = '' second_name= '' ... you can then define: worker1 = employee() worker1.first_name = 'Tim' ... In function calls, you will simply pass worker, rather than all the variables. Classes can contain more, they can also contain functions, and normally one should include such functions that manipulate the data associated: class employee: def __init__(self, first_name, second_name): self.first_name = first_name self.second_name= secound_name ... def social_sec_no(self): get social security number from ZODB and return it. ... The following example implements a small database with classes. Personally, I recommend not to design a database wherein the records are instances of classes. It will complicate the debugging and error checking, because utilities such as pprint.pprint() will not list the contents of classes. Also, classes have the tendency to increase the storage required, because all variables contained in the class are saved. This will either limit the use of variables in side the class or incrase storage. All this can, of course, be overcome by proper coding, but for a beginner this may be too difficult (It took me quite some time to get into classes.) Note that I am not a believer in storing classes, but I believe very much in retrieving data and putting it into classes and then to work with these. However, everyone has her/his own preferences. Here is a small database composed of class instances: """ class Entry: def setName(self, name): self.nam = name def setTel(self,tel): self.tel = tel def classes0(): data_base = open_zodb(PATH) try: data_base[0] ['tel'] = {} person = Entry() person.setName('Mary') person.setTel('123') print person.nam print person.tel data_base[0] ['tel'] [person.nam] = person save_zodb(data_base,'saved mary as person','me') finally: close_zodb(data_base) def classes1(): data_base = open_zodb(PATH) try: pprint.pprint( data_base[0] ) print data_base[0] ['tel'] ['Mary'].nam print data_base[0] ['tel'] ['Mary'].tel finally: close_zodb(data_base) """ Note that this is nothing magic. If you read the articles suggested at the beginning of this module, you will note that Entry is not sub-classed from Persistence (class Entry(Persistence.Persistent) ). Since save_zodb() will set the _p_changed flag, there is no advantage in this example in using a persistent class, because all that is gained by using persistent classes is that it will notify ZODB of its changes, rather than this needs to be done by executing _p_changed = 1. """ # **************************************************************************** # Chapter 8: # BTrees: Tuning your ZODB # # **************************************************************************** # # For large databases (millions of records), the ZODB provides the option to # define sub-branches of the root object as BTrees. # There are several types of BTrees, depending on # what keys (not information !) your database will contain. # Please refer to: 'Advanced ZODB for Python Programmers' # at http://zope.org/Documentation/Articles/ZODB2 # For a more detailed introduction. # # All you have to do, to define a branch of the ZODB as a BTree is to # define it as an instance of the pertinent BTree class: from BTrees.OOBTree import OOBTree def BTree(): db = open_zodb() if db[0].has_key('btree') == False: db[0] ['btree'] = OOBTree() db[0] ['btree'] ['test'] = 'A first element' pprint.pprint( db[0] ) close_zodb(db) # Note that the BTree is only defined once, since otherwise, it would be # constantly defined a new, over-writing the old definition. Once defined # and committed, the BTree object is persistent and needs no new definition. # # NOTE that you have to choose a BTree according to your key structure. # You should not mix the types of keys used in your BTrees.