This is a very simple script to allow someone to examine a ZODB database. Start the script and you can see what is in your ZODB file as well as the overall structure.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 | """wxView.py - a simple view for ZODB files
TODO:
-Support ZEO
-Rewrite/extend to use the builtin HTTP server a la pydoc
"""
import UserDict
import UserList
import locale
import os
import os.path
import sys
import wx
import ZODB
from ZODB import FileStorage, DB
from persistent import Persistent
from BTrees.OOBTree import OOBTree
from persistent.list import PersistentList as PList
from persistent.mapping import PersistentMapping as PMap
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
def open_zodb(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 save_pos(win, cfg):
"""Save a window position to the registry"""
(xpos, ypos) = win.GetPositionTuple()
(width, height) = win.GetSizeTuple()
cfg.WriteInt('xpos', xpos)
cfg.WriteInt('ypos', ypos)
cfg.WriteInt('width', width)
cfg.WriteInt('height', height)
def set_pos(win, cfg):
"""Restore a window to a position from the registry"""
xpos = cfg.ReadInt('xpos', -1)
ypos = cfg.ReadInt('ypos', -1)
width = cfg.ReadInt('width', -1)
height = cfg.ReadInt('height', -1)
win.SetDimensions(xpos, ypos, width, height)
class ZODBFrame(wx.Frame):
def __init__(self, *args, **kwds):
# begin wxGlade: ZODBFrame.__init__
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.window_1 = wx.SplitterWindow(self, -1,
style=wx.SP_3D|wx.SP_BORDER)
self.panel_1 = wx.Panel(self.window_1, -1)
self.window_1_pane_1 = wx.Panel(self.window_1, -1)
# Menu Bar
self.mb = wx.MenuBar()
self.SetMenuBar(self.mb)
self.mnuFile = wx.Menu()
self.mnuOpen = wx.MenuItem(self.mnuFile, wx.ID_OPEN, "&Open\tCtrl-O",
"", wx.ITEM_NORMAL)
self.mnuFile.AppendItem(self.mnuOpen)
self.mnuFile.Append(wx.ID_CLOSE, "&Close", "", wx.ITEM_NORMAL)
self.mnuFile.AppendSeparator()
self.mnuFile.Append(wx.ID_EXIT, "E&xit", "", wx.ITEM_NORMAL)
self.mb.Append(self.mnuFile, "&File")
# Menu Bar end
self.sb = self.CreateStatusBar(1, wx.ST_SIZEGRIP)
self.db_layout_tree = wx.TreeCtrl(self.window_1_pane_1, -1,
style=wx.TR_HAS_BUTTONS|
wx.TR_LINES_AT_ROOT|
wx.TR_DEFAULT_STYLE|
wx.SUNKEN_BORDER)
self.label_1 = wx.StaticText(self.panel_1, -1, "Data Type:")
self.txtType = wx.StaticText(self.panel_1, -1, "txtType")
self.txtData = wx.TextCtrl(self.panel_1, -1, "", style=wx.TE_MULTILINE)
self.__set_properties()
self.__do_layout()
# end wxGlade
self.__create_image_list()
self.__create_file_history()
self.__set_window_position()
self.__set_bindings()
self.db = None
self.root = None
def __create_file_history(self):
self.file_history = wx.FileHistory()
self.file_history.UseMenu(self.mnuFile)
old_path = self.wxcfg.GetPath()
self.wxcfg.SetPath('/RecentFiles')
self.file_history.Load(self.wxcfg)
self.wxcfg.SetPath(old_path)
self._need_save = False
def __create_image_list(self):
"""Setup our image list for the tree control"""
isz = (16, 16)
il = wx.ImageList(*isz)
self.folder_idx = il.Add(wx.ArtProvider_GetBitmap(wx.ART_FOLDER,
wx.ART_OTHER, isz))
self.folder_open_idx = il.Add(wx.ArtProvider_GetBitmap(wx.ART_FILE_OPEN,
wx.ART_OTHER, isz))
self.file_idx = il.Add(wx.ArtProvider_GetBitmap(wx.ART_REPORT_VIEW,
wx.ART_OTHER, isz))
self.il = il
def __set_bindings(self):
self.Bind(wx.EVT_CLOSE, self.onExit)
self.Bind(wx.EVT_MENU, self.onExit, id=wx.ID_EXIT)
self.Bind(wx.EVT_MENU, self.doOpen, id=wx.ID_OPEN)
self.Bind(wx.EVT_MENU, self.doClose, id=wx.ID_CLOSE)
self.Bind(wx.EVT_MENU_RANGE, self.doFileHistory, id=wx.ID_FILE1,
id2=wx.ID_FILE9)
self.db_layout_tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.onSelChange)
def __set_properties(self):
# begin wxGlade: ZODBFrame.__set_properties
self.SetTitle("ZODB Viewer")
self.sb.SetStatusWidths([-1])
# statusbar fields
sb_fields = [""]
for i in range(len(sb_fields)):
self.sb.SetStatusText(sb_fields[i], i)
self.txtType.SetFont(wx.Font(12, wx.MODERN, wx.NORMAL, wx.BOLD, 0,
"Courier New"))
self.txtData.SetFont(wx.Font(12, wx.MODERN, wx.NORMAL, wx.NORMAL, 0,
"Courier New"))
# end wxGlade
def __set_window_position(self):
self.wxcfg = wx.Config()
old_path = self.wxcfg.GetPath()
self.wxcfg.SetPath('/Window Information')
set_pos(self, self.wxcfg)
self.wxcfg.SetPath(old_path)
def __do_layout(self):
# begin wxGlade: ZODBFrame.__do_layout
sizer_1 = wx.BoxSizer(wx.HORIZONTAL)
grid_sizer_2 = wx.FlexGridSizer(2, 1, 5, 0)
grid_sizer_3 = wx.FlexGridSizer(1, 2, 0, 5)
sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
sizer_2.Add(self.db_layout_tree, 1, wx.EXPAND, 0)
self.window_1_pane_1.SetAutoLayout(True)
self.window_1_pane_1.SetSizer(sizer_2)
sizer_2.Fit(self.window_1_pane_1)
sizer_2.SetSizeHints(self.window_1_pane_1)
grid_sizer_3.Add(self.label_1, 0, wx.FIXED_MINSIZE, 0)
grid_sizer_3.Add(self.txtType, 0, wx.FIXED_MINSIZE, 0)
grid_sizer_3.AddGrowableCol(1)
grid_sizer_2.Add(grid_sizer_3, 1, wx.EXPAND, 0)
grid_sizer_2.Add(self.txtData, 0, wx.EXPAND|wx.FIXED_MINSIZE, 0)
self.panel_1.SetAutoLayout(True)
self.panel_1.SetSizer(grid_sizer_2)
grid_sizer_2.Fit(self.panel_1)
grid_sizer_2.SetSizeHints(self.panel_1)
grid_sizer_2.AddGrowableRow(1)
grid_sizer_2.AddGrowableCol(0)
self.window_1.SplitVertically(self.window_1_pane_1, self.panel_1)
sizer_1.Add(self.window_1, 1, wx.EXPAND, 0)
self.SetAutoLayout(True)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
sizer_1.SetSizeHints(self)
self.Layout()
# end wxGlade
def _set_child_icons(self, c, d):
"""Set the appropriate icons for a given tree child
c
The child we are updating
d
The data associated with the child
"""
if isinstance(d, (dict, UserDict.UserDict, OOBTree)):
self.db_layout_tree.SetItemImage(c,
self.folder_idx,
wx.TreeItemIcon_Normal)
self.db_layout_tree.SetItemImage(c,
self.folder_open_idx,
wx.TreeItemIcon_Expanded)
else:
self.db_layout_tree.SetItemImage(c, self.file_idx,
wx.TreeItemIcon_Normal)
def createTree(self, filename):
"""Create a new tree structure for when we open a file"""
self.doClose()
self.db = open_zodb(filename)
self.db_layout_tree.SetImageList(self.il)
self.root = self.db_layout_tree.AddRoot(os.path.basename(filename))
self.db_layout_tree.SetPyData(self.root, self.db[0])
self.db_layout_tree.SetItemImage(self.root, self.folder_idx,
wx.TreeItemIcon_Normal)
self.db_layout_tree.SetItemImage(self.root, self.folder_open_idx,
wx.TreeItemIcon_Expanded)
db = self.db[0]
for key in db.keys():
child = self.db_layout_tree.AppendItem(self.root, key)
self.db_layout_tree.SetPyData(child, db[key])
if isinstance(db[key], dict) or isinstance(db[key], list):
self.db_layout_tree.SetItemImage(child, self.folder_idx,
wx.TreeItemIcon_Normal)
self.db_layout_tree.SetItemImage(child, self.folder_open_idx,
wx.TreeItemIcon_Expanded)
else:
self.db_layout_tree.SetItemImage(child, self.file_idx,
wx.TreeItemIcon_Normal)
self.db_layout_tree.Expand(self.root)
def doClose(self, *event):
"""Close the current file and clear the screen"""
if self.db:
close_zodb(self.db)
self.db = None
if self.root:
self.db_layout_tree.DeleteAllItems()
self.txtType.SetLabel('')
self.txtData.Clear()
def doFileHistory(self, event):
"""Open a file from file history"""
file_number = event.GetId() - wx.ID_FILE1
filename = self.file_history.GetHistoryFile(file_number)
self.createTree(filename)
def doOpen(self, *event):
"""Open a file from the file system"""
# Select and open the ZODB file object.
dlg = wx.FileDialog(self, message="Choose a file",
defaultFile="", style=wx.OPEN | wx.CHANGE_DIR)
if dlg.ShowModal() == wx.ID_OK:
# This returns a Python list of files that were selected.
filename = dlg.GetPath()
self.createTree(filename)
self.file_history.AddFileToHistory(filename)
dlg.Destroy()
def onExit(self, event):
"""Exit the program"""
self.doClose()
old_path = self.wxcfg.GetPath()
self.wxcfg.SetPath('/RecentFiles')
self.file_history.Save(self.wxcfg)
self.wxcfg.SetPath(old_path)
old_path = self.wxcfg.GetPath()
self.wxcfg.SetPath('/Window Information')
save_pos(self, self.wxcfg)
self.wxcfg.SetPath(old_path)
self.Destroy()
def onSelChange(self, event):
"""Select a new tree node, loading it if needed"""
item = event.GetItem()
data = self.db_layout_tree.GetPyData(item)
if isinstance(data, (dict, UserDict.UserDict, OOBTree)):
self.txtData.Clear()
self.txtType.SetLabel(str(type(data)))
if hasattr(data, 'wx_str'):
self.txtData.AppendText(data.wx_str())
if not self.db_layout_tree.ItemHasChildren(item):
keys = data.keys()
try:
keys.sort()
except AttributeError:
pass
for key in keys:
child = self.db_layout_tree.AppendItem(item, str(key))
self.db_layout_tree.SetPyData(child, data[key])
self._set_child_icons(child, data[key])
elif isinstance(data, (list, UserList.UserList)):
self.txtData.Clear()
self.txtType.SetLabel(str(type(data)))
for d in data:
self.txtData.AppendText(str(type(d)) + ' --\n')
if hasattr(d, 'wx_str'):
self.txtData.AppendText(d.wx_str())
else:
self.txtData.AppendText(str(d))
self.txtData.AppendText('\n')
else:
self.txtType.SetLabel(str(type(data)))
fmt = '%s\n-----\n%s'
if hasattr(data, 'wx_str'):
self.txtData.SetValue(data.wx_str())
else:
self.txtData.SetValue(str(data))
# end of class ZODBFrame
if __name__ == "__main__":
locale.setlocale(locale.LC_ALL, '')
wxviewdb = wx.PySimpleApp(0)
wx.InitAllImageHandlers()
frmZODB = ZODBFrame(None, -1, "")
wxviewdb.SetTopWindow(frmZODB)
frmZODB.Show()
wxviewdb.MainLoop()
|
I wrote this because I've done a bad job of documenting my database. The code is fine, however when I needed to go back and extract data in a different way I sometimes was having problems figuring out what I did. Worse, for me, is that I now have someone else helping on the codebase and they needed a simple way to see how the data was laid out.
When the data is displayed the script first looks for a method called 'wx_str'. This method should return a text representation of the object. I chose this instead of using __str__ since I use __str__ for other things and I am considering allowing wx_str to return a more complex data representation for fancier output. Obviously, if wx_str isn't available __str__ is used.
On the todo side I think allowing other connection methods (say ZEO) would be a good addition, as well as making this a bit more robust. We'll see what time permits. :)
4/8/2005: Updated with a slight change in formatting to get line length down to 79 chars
Great contribution! Slight startup error.
Similar issue, different solution. I had the same issue. I added self.wxcfg = wx.Config() to __init__(), and was able to use it. <p></p> Also, a few things to note:
This uses the latest persistent.Persistent from Zope3 instead of the old Persistence.Persistent.
You will need to import your Persistent subclasses (the ones you defined in which to store your data) in order for them to be "opened" in the tree by this script.
ZEO Support. I've hacked in ZEO support -- was pretty straight forward. Added a new menu item, did an import of ZEO.ClientStorage, added a few checks, etc. Very cool, very useful little tool -- thanks for sharing!
Would you share your ZEO addon? Hi Duncan,
would be nice to see your zeo-addon as a recontribution :)
is this possible?
thanks Peter
ZEO Hack. Sure... let me find it...
Okay, you'll need this import:
I then modified the open_zodb() function:
To ZODBFrame.__init__(), I added this:
I modified ZODBFrame.creatTree() to include this:
And, finally, I added this method:
I may have midded something... and this was done against the first version of the recipe...
I think, there should be another line. It is a great program. I think, there should be a line in the _set_binding() method:
self.Bind(wx, EVT_MENU, self.doOpenUrl, id=wx.ID_OK
Exporting wiki contents from ZODB to MYSQL. Please do me a favor. I am looking for a python script that can export wiki contents which is stored in ZODB to MYSQL.
Thanks
As Duncan said 1. self.wxcfg = wx.Config() to __init__()
and, then
The crashing is due to a new module name change in ZODB 3.6
Hi, i've download your recipe and try to run it but have several errors on traceback: File "C:/Program Files/Python 2.6.2/recipe-409012-2.py", line 14, in <module> import zodb ImportError: No module named zodb.
Please, tell me, where I can get this module? I've used a zodb docflow system and try to connect to it's database. Python 2.6.2 and wxPython 2.8 for Python 2.6 also installed.