initial commit
This commit is contained in:
17
.project
Normal file
17
.project
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>zodb_tools</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.python.pydev.PyDevBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.python.pydev.pythonNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
9
.pydevproject
Normal file
9
.pydevproject
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?eclipse-pydev version="1.0"?><pydev_project>
|
||||
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
|
||||
<path>/${PROJECT_DIR_NAME}/src</path>
|
||||
<path>/${PROJECT_DIR_NAME}/test</path>
|
||||
</pydev_pathproperty>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 3.0</pydev_property>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
|
||||
</pydev_project>
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Andreas Balogh
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
75
src/tk_viewer.py
Normal file
75
src/tk_viewer.py
Normal file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/python3.5
|
||||
# encoding: utf8
|
||||
|
||||
# Copyright (c) 2016 Andreas
|
||||
# See LICENSE for details.
|
||||
|
||||
""" tk ZODB viewer ui """
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from configparser import ConfigParser
|
||||
import logging
|
||||
import os
|
||||
from pprint import pprint
|
||||
import socket
|
||||
import sys
|
||||
|
||||
from BTrees.OOBTree import OOBTree
|
||||
from persistent.list import PersistentList as PList
|
||||
from persistent.mapping import PersistentMapping as PDict
|
||||
import transaction
|
||||
from zcm import ZDatabase, ZConnection
|
||||
from threading import Thread, Event
|
||||
|
||||
import datetime as dt
|
||||
from queue import Queue
|
||||
|
||||
# from BTrees.IOBTree import IOBTree
|
||||
# from BTrees.IOBTree import IOBTree
|
||||
# from persistent.list import PersistentList as PList
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def gui(argv=None):
|
||||
""" command line interface """
|
||||
if argv is None:
|
||||
argv = sys.argv
|
||||
# parse options and arguments
|
||||
parser = ArgumentParser(description="tumblr post incremental photo ripper")
|
||||
parser.add_argument("command", choices=["pack", "load_mdh"], help="command")
|
||||
parser.add_argument("--config", default="../etc/config.ini",
|
||||
help="machine configuration file [default: %(default)s]")
|
||||
parser.add_argument(
|
||||
"--zmd", default="../var/md.zodb", help="zodb market data [default: %(default)s]")
|
||||
parser.add_argument("-v", "--verbose", action="store_true", help="debug output")
|
||||
args = parser.parse_args(argv[1:])
|
||||
argd = vars(args)
|
||||
# main program
|
||||
LOG.info("%s %s", os.path.basename(argv[0]), " ".join(argv[1:]))
|
||||
# process config file
|
||||
config = ConfigParser()
|
||||
try:
|
||||
config.read(args.config)
|
||||
except OSError:
|
||||
LOG.warn("config file {} missing, using defaults".format(args.config))
|
||||
else:
|
||||
hostname = socket.gethostname()
|
||||
try:
|
||||
argd.update(config[hostname])
|
||||
except KeyError:
|
||||
LOG.warn("no section for {} in {}".format(hostname, args.config))
|
||||
# debug logging
|
||||
if args.verbose:
|
||||
LOG.setLevel(logging.DEBUG)
|
||||
pprint(argd)
|
||||
# prepare queues and threads
|
||||
func = globals()[args.command]
|
||||
func(args)
|
||||
LOG.info("done.")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format='%(asctime)s.%(msecs)03i [%(thread)i] %(levelname).4s %(funcName)10s: %(message)s',
|
||||
datefmt='%H:%M:%S')
|
||||
sys.exit(gui())
|
||||
330
src/wx_viewer.py
Normal file
330
src/wx_viewer.py
Normal file
@@ -0,0 +1,330 @@
|
||||
# {{{ http://code.activestate.com/recipes/409012/ (r2)
|
||||
"""wxView.py - a simple view for ZODB files
|
||||
|
||||
TODO:
|
||||
-Support ZEO
|
||||
-Rewrite/extend to use the builtin HTTP server a la pydoc
|
||||
"""
|
||||
from BTrees.OOBTree import OOBTree
|
||||
from ZODB import FileStorage, DB
|
||||
from persistent.list import PersistentList as PList
|
||||
from persistent.mapping import PersistentMapping as PDict
|
||||
import UserDict
|
||||
import collections
|
||||
import locale
|
||||
import os
|
||||
import transaction
|
||||
import wx
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
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)
|
||||
|
||||
self.wxcfg = wx.Config()
|
||||
|
||||
# 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 list(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 = list(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, collections.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()
|
||||
97
src/zcm.py
Normal file
97
src/zcm.py
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/python3.5
|
||||
# encoding: utf8
|
||||
|
||||
# Copyright (c) 2016 Andreas
|
||||
# See LICENSE for details.
|
||||
|
||||
""" ZODB context manager """
|
||||
|
||||
# from BTrees.IOBTree import IOBTree
|
||||
# from BTrees.OOBTree import OOBTree
|
||||
# from persistent import Persistent
|
||||
# from persistent.list import PersistentList as PList
|
||||
# from persistent.mapping import PersistentMapping as PDict
|
||||
|
||||
from ZEO.ClientStorage import ClientStorage
|
||||
from ZODB import DB
|
||||
from ZODB.FileStorage import FileStorage
|
||||
|
||||
|
||||
class ZDatabase():
|
||||
""" Provides a ZODB database context manager """
|
||||
|
||||
def __init__(self, uri, **kwargs):
|
||||
self.storage = create_storage(uri)
|
||||
self.db = DB(self.storage, **kwargs)
|
||||
|
||||
def __enter__(self):
|
||||
return self.db
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.db.close()
|
||||
return False
|
||||
|
||||
|
||||
class ZConnection():
|
||||
""" Provides a ZODB connection with auto-abort (default).
|
||||
Provides a tuple of connection and root object:
|
||||
with ZConnection(db) as (cx, root):
|
||||
root.one = "ok"
|
||||
ZConnection implements a connection context manager.
|
||||
Transaction context managers in contrast do auto-commit:
|
||||
a) with db.transaction() as connection, or
|
||||
b) with cx.transaction_manager as transaction, or
|
||||
c) with transaction.manager as transaction (for the thread-local transaction manager)
|
||||
See also http://www.zodb.org/en/latest/guide/transactions-and-threading.html
|
||||
"""
|
||||
def __init__(self, db, auto_commit=False, transaction_manager=None):
|
||||
self.db = db
|
||||
self.auto_commit = auto_commit
|
||||
self.transaction_manager = transaction_manager
|
||||
self.cx = None
|
||||
|
||||
def __enter__(self):
|
||||
if self.transaction_manager:
|
||||
self.cx = self.db.open(self.transaction_manager)
|
||||
else:
|
||||
self.cx = self.db.open()
|
||||
return self.cx, self.cx.root()
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
if self.auto_commit:
|
||||
self.cx.transaction_manager.commit()
|
||||
self.cx.close()
|
||||
return False
|
||||
|
||||
|
||||
def create_storage(uri):
|
||||
""" supported URIs
|
||||
file://e:/workspaces/zeo/bots.fs
|
||||
zeo://localhost:8001
|
||||
e:/workspaces/zeo/bots.fs
|
||||
@see https://en.wikipedia.org/wiki/Uniform_Resource_Identifier
|
||||
"""
|
||||
if uri.startswith("file://"):
|
||||
storage = FileStorage(uri[7:])
|
||||
elif uri.startswith("zeo://"):
|
||||
addr, port = uri[6:].split(":")
|
||||
# addr_ = addr.encode("ASCII")
|
||||
storage = ClientStorage((addr, int(port)))
|
||||
else:
|
||||
storage = FileStorage(uri)
|
||||
return storage
|
||||
|
||||
|
||||
def database(uri):
|
||||
""" convenience function for single thread, return one connection from the pool """
|
||||
storage = create_storage(uri)
|
||||
db = DB(storage)
|
||||
return db
|
||||
|
||||
|
||||
def connection(db):
|
||||
""" Convenience function for multi thread, returns
|
||||
connection, transaction manager and root
|
||||
"""
|
||||
cx = db.open()
|
||||
return cx, cx.root()
|
||||
109
test/test_zcm.py
Normal file
109
test/test_zcm.py
Normal file
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/python3.5
|
||||
# encoding: utf8
|
||||
|
||||
"""Test zodb database class"""
|
||||
|
||||
from pprint import pprint
|
||||
import unittest
|
||||
import transaction
|
||||
import ZODB.POSException
|
||||
|
||||
import zcm
|
||||
|
||||
|
||||
FILE = r"..\var\unittest.zodb"
|
||||
|
||||
|
||||
class TestContext(unittest.TestCase):
|
||||
|
||||
def test_sync(self):
|
||||
print("sync")
|
||||
db = zcm.database(FILE)
|
||||
cx = db.open()
|
||||
cx.root.one = 2
|
||||
print(cx.root.one)
|
||||
with db.transaction() as cx2:
|
||||
_ = cx2.transaction_manager
|
||||
root = cx2.root()
|
||||
root['one'] = 3
|
||||
transaction.abort()
|
||||
print(cx.root.one)
|
||||
cx.root.one = 4
|
||||
print(cx.root.one)
|
||||
db.close()
|
||||
|
||||
def test_transaction(self):
|
||||
print("transaction")
|
||||
db = zcm.database(FILE)
|
||||
cx = db.open()
|
||||
cx.root.one = 2
|
||||
print(cx.root.one)
|
||||
with db.transaction() as cx2:
|
||||
_ = cx2.transaction_manager
|
||||
root = cx2.root()
|
||||
root['one'] = 3
|
||||
print(cx.root.one)
|
||||
cx.root.one = 4
|
||||
print(cx.root.one)
|
||||
cx3 = db.open()
|
||||
print(cx3.root.one)
|
||||
db.close()
|
||||
|
||||
def test_conflict(self):
|
||||
db = zcm.database(FILE)
|
||||
try:
|
||||
cx = db.open()
|
||||
cx.root.one = 2
|
||||
print(cx.root.one)
|
||||
with db.transaction() as cx2:
|
||||
_ = cx2.transaction_manager
|
||||
root = cx2.root()
|
||||
root['one'] = 3
|
||||
print(cx.root.one)
|
||||
cx.root.one = 4
|
||||
print(cx.root.one)
|
||||
with self.assertRaises(ZODB.POSException.ConflictError):
|
||||
transaction.commit()
|
||||
finally:
|
||||
db.close()
|
||||
db = zcm.database(FILE)
|
||||
cx = db.open()
|
||||
print(cx.root.one)
|
||||
db.close()
|
||||
|
||||
def test_connection(self):
|
||||
db = zcm.database(FILE)
|
||||
txm = transaction.TransactionManager()
|
||||
cx3 = db.open(txm)
|
||||
print(cx3.root.another)
|
||||
cx3.root.another = "no tx"
|
||||
print(cx3.root.another)
|
||||
with txm:
|
||||
cx3.root.another = "object"
|
||||
print(cx3.root.another)
|
||||
db.close()
|
||||
|
||||
def test_default_txm(self):
|
||||
print("default_txm")
|
||||
db = zcm.database(FILE)
|
||||
cx2, root = zcm.connection(db)
|
||||
cx3 = db.open()
|
||||
if cx2.transaction_manager is cx3.transaction_manager:
|
||||
print("cx3 identity")
|
||||
print(root.another)
|
||||
with cx2.transaction_manager:
|
||||
root.another = "more"
|
||||
print(cx3.root.another)
|
||||
with db.transaction() as cx4:
|
||||
cx4.root.another = "other"
|
||||
if cx4.transaction_manager == cx2.transaction_manager:
|
||||
print("cx4 same")
|
||||
else:
|
||||
print("cx4 differs")
|
||||
cx2.transaction_manager.abort()
|
||||
print(root.another)
|
||||
with cx3.transaction_manager:
|
||||
cx3.root.another = "object"
|
||||
print(root.another)
|
||||
print(cx3.root.another)
|
||||
db.close()
|
||||
Reference in New Issue
Block a user