New: Tournament Results Viewer
Decided to see how hard it would be to get the tournament results using the existing schema. Turns out the cash graph query is pretty similar and not much thought was required. Created a new tab based on the existing cash game viewer for profit. This is probably the idea page for multiple axis to be added. - Distributions for position - distributions by buy-in level - ROI per buy-in level Lots of stuff like that.
This commit is contained in:
parent
b7c94781ef
commit
cf2629b290
319
pyfpdb/GuiTourneyGraphViewer.py
Normal file
319
pyfpdb/GuiTourneyGraphViewer.py
Normal file
|
@ -0,0 +1,319 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#Copyright 2008-2010 Carl Gherardi
|
||||||
|
#This program is free software: you can redistribute it and/or modify
|
||||||
|
#it under the terms of the GNU Affero General Public License as published by
|
||||||
|
#the Free Software Foundation, version 3 of the License.
|
||||||
|
#
|
||||||
|
#This program is distributed in the hope that it will be useful,
|
||||||
|
#but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
#GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
#You should have received a copy of the GNU Affero General Public License
|
||||||
|
#along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#In the "official" distribution you can find the license in agpl-3.0.txt.
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import pygtk
|
||||||
|
pygtk.require('2.0')
|
||||||
|
import gtk
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
from time import *
|
||||||
|
from datetime import datetime
|
||||||
|
#import pokereval
|
||||||
|
|
||||||
|
import locale
|
||||||
|
lang=locale.getdefaultlocale()[0][0:2]
|
||||||
|
if lang=="en":
|
||||||
|
def _(string): return string
|
||||||
|
else:
|
||||||
|
import gettext
|
||||||
|
try:
|
||||||
|
trans = gettext.translation("fpdb", localedir="locale", languages=[lang])
|
||||||
|
trans.install()
|
||||||
|
except IOError:
|
||||||
|
def _(string): return string
|
||||||
|
|
||||||
|
import fpdb_import
|
||||||
|
import Database
|
||||||
|
import Filters
|
||||||
|
import Charset
|
||||||
|
|
||||||
|
try:
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use('GTKCairo')
|
||||||
|
from matplotlib.figure import Figure
|
||||||
|
from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas
|
||||||
|
from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar
|
||||||
|
from matplotlib.font_manager import FontProperties
|
||||||
|
from numpy import arange, cumsum
|
||||||
|
from pylab import *
|
||||||
|
except ImportError, inst:
|
||||||
|
print _("""Failed to load libs for graphing, graphing will not function. Please
|
||||||
|
install numpy and matplotlib if you want to use graphs.""")
|
||||||
|
print _("""This is of no consequence for other parts of the program, e.g. import
|
||||||
|
and HUD are NOT affected by this problem.""")
|
||||||
|
print "ImportError: %s" % inst.args
|
||||||
|
|
||||||
|
class GuiTourneyGraphViewer (threading.Thread):
|
||||||
|
|
||||||
|
def __init__(self, querylist, config, parent, debug=True):
|
||||||
|
"""Constructor for GraphViewer"""
|
||||||
|
self.sql = querylist
|
||||||
|
self.conf = config
|
||||||
|
self.debug = debug
|
||||||
|
self.parent = parent
|
||||||
|
#print "start of GraphViewer constructor"
|
||||||
|
self.db = Database.Database(self.conf, sql=self.sql)
|
||||||
|
|
||||||
|
|
||||||
|
filters_display = { "Heroes" : True,
|
||||||
|
"Sites" : True,
|
||||||
|
"Games" : False,
|
||||||
|
"Limits" : False,
|
||||||
|
"LimitSep" : False,
|
||||||
|
"LimitType" : False,
|
||||||
|
"Type" : False,
|
||||||
|
"UseType" : 'tour',
|
||||||
|
"Seats" : False,
|
||||||
|
"SeatSep" : False,
|
||||||
|
"Dates" : True,
|
||||||
|
"Groups" : False,
|
||||||
|
"Button1" : True,
|
||||||
|
"Button2" : True
|
||||||
|
}
|
||||||
|
|
||||||
|
self.filters = Filters.Filters(self.db, self.conf, self.sql, display = filters_display)
|
||||||
|
self.filters.registerButton1Name("Refresh _Graph")
|
||||||
|
self.filters.registerButton1Callback(self.generateGraph)
|
||||||
|
self.filters.registerButton2Name("_Export to File")
|
||||||
|
self.filters.registerButton2Callback(self.exportGraph)
|
||||||
|
|
||||||
|
self.mainHBox = gtk.HBox(False, 0)
|
||||||
|
self.mainHBox.show()
|
||||||
|
|
||||||
|
self.leftPanelBox = self.filters.get_vbox()
|
||||||
|
|
||||||
|
self.hpane = gtk.HPaned()
|
||||||
|
self.hpane.pack1(self.leftPanelBox)
|
||||||
|
self.mainHBox.add(self.hpane)
|
||||||
|
# hierarchy: self.mainHBox / self.hpane / self.graphBox / self.canvas / self.fig / self.ax
|
||||||
|
|
||||||
|
self.graphBox = gtk.VBox(False, 0)
|
||||||
|
self.graphBox.show()
|
||||||
|
self.hpane.pack2(self.graphBox)
|
||||||
|
self.hpane.show()
|
||||||
|
|
||||||
|
self.fig = None
|
||||||
|
#self.exportButton.set_sensitive(False)
|
||||||
|
self.canvas = None
|
||||||
|
|
||||||
|
|
||||||
|
self.db.rollback()
|
||||||
|
|
||||||
|
def get_vbox(self):
|
||||||
|
"""returns the vbox of this thread"""
|
||||||
|
return self.mainHBox
|
||||||
|
#end def get_vbox
|
||||||
|
|
||||||
|
def clearGraphData(self):
|
||||||
|
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
if self.canvas:
|
||||||
|
self.graphBox.remove(self.canvas)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if self.fig != None:
|
||||||
|
self.fig.clear()
|
||||||
|
self.fig = Figure(figsize=(5,4), dpi=100)
|
||||||
|
if self.canvas is not None:
|
||||||
|
self.canvas.destroy()
|
||||||
|
|
||||||
|
self.canvas = FigureCanvas(self.fig) # a gtk.DrawingArea
|
||||||
|
except:
|
||||||
|
err = traceback.extract_tb(sys.exc_info()[2])[-1]
|
||||||
|
print _("***Error: ")+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
|
||||||
|
raise
|
||||||
|
|
||||||
|
def generateGraph(self, widget, data):
|
||||||
|
try:
|
||||||
|
self.clearGraphData()
|
||||||
|
|
||||||
|
sitenos = []
|
||||||
|
playerids = []
|
||||||
|
|
||||||
|
sites = self.filters.getSites()
|
||||||
|
heroes = self.filters.getHeroes()
|
||||||
|
siteids = self.filters.getSiteIds()
|
||||||
|
|
||||||
|
# Which sites are selected?
|
||||||
|
for site in sites:
|
||||||
|
if sites[site] == True:
|
||||||
|
sitenos.append(siteids[site])
|
||||||
|
_hname = Charset.to_utf8(heroes[site])
|
||||||
|
result = self.db.get_player_id(self.conf, site, _hname)
|
||||||
|
if result is not None:
|
||||||
|
playerids.append(int(result))
|
||||||
|
|
||||||
|
if not sitenos:
|
||||||
|
#Should probably pop up here.
|
||||||
|
print _("No sites selected - defaulting to PokerStars")
|
||||||
|
self.db.rollback()
|
||||||
|
return
|
||||||
|
|
||||||
|
if not playerids:
|
||||||
|
print _("No player ids found")
|
||||||
|
self.db.rollback()
|
||||||
|
return
|
||||||
|
|
||||||
|
#Set graph properties
|
||||||
|
self.ax = self.fig.add_subplot(111)
|
||||||
|
|
||||||
|
#Get graph data from DB
|
||||||
|
starttime = time()
|
||||||
|
green = self.getData(playerids, sitenos)
|
||||||
|
print _("Graph generated in: %s") %(time() - starttime)
|
||||||
|
|
||||||
|
|
||||||
|
#Set axis labels and grid overlay properites
|
||||||
|
self.ax.set_xlabel(_("Tournaments"), fontsize = 12)
|
||||||
|
self.ax.set_ylabel("$", fontsize = 12)
|
||||||
|
self.ax.grid(color='g', linestyle=':', linewidth=0.2)
|
||||||
|
if green == None or green == []:
|
||||||
|
self.ax.set_title(_("No Data for Player(s) Found"))
|
||||||
|
green = ([ 0., 0., 0., 0., 500., 1000., 900., 800.,
|
||||||
|
700., 600., 500., 400., 300., 200., 100., 0.,
|
||||||
|
500., 1000., 1000., 1000., 1000., 1000., 1000., 1000.,
|
||||||
|
1000., 1000., 1000., 1000., 1000., 1000., 875., 750.,
|
||||||
|
625., 500., 375., 250., 125., 0., 0., 0.,
|
||||||
|
0., 500., 1000., 900., 800., 700., 600., 500.,
|
||||||
|
400., 300., 200., 100., 0., 500., 1000., 1000.])
|
||||||
|
red = ([ 0., 0., 0., 0., 500., 1000., 900., 800.,
|
||||||
|
700., 600., 500., 400., 300., 200., 100., 0.,
|
||||||
|
0., 0., 0., 0., 0., 0., 125., 250.,
|
||||||
|
375., 500., 500., 500., 500., 500., 500., 500.,
|
||||||
|
500., 500., 375., 250., 125., 0., 0., 0.,
|
||||||
|
0., 500., 1000., 900., 800., 700., 600., 500.,
|
||||||
|
400., 300., 200., 100., 0., 500., 1000., 1000.])
|
||||||
|
blue = ([ 0., 0., 0., 0., 500., 1000., 900., 800.,
|
||||||
|
700., 600., 500., 400., 300., 200., 100., 0.,
|
||||||
|
0., 0., 0., 0., 0., 0., 125., 250.,
|
||||||
|
375., 500., 625., 750., 875., 1000., 875., 750.,
|
||||||
|
625., 500., 375., 250., 125., 0., 0., 0.,
|
||||||
|
0., 500., 1000., 900., 800., 700., 600., 500.,
|
||||||
|
400., 300., 200., 100., 0., 500., 1000., 1000.])
|
||||||
|
|
||||||
|
self.ax.plot(green, color='green', label=_('Tournaments: %d\nProfit: $%.2f') %(len(green), green[-1]))
|
||||||
|
#self.ax.plot(blue, color='blue', label=_('Showdown: $%.2f') %(blue[-1]))
|
||||||
|
#self.ax.plot(red, color='red', label=_('Non-showdown: $%.2f') %(red[-1]))
|
||||||
|
self.graphBox.add(self.canvas)
|
||||||
|
self.canvas.show()
|
||||||
|
self.canvas.draw()
|
||||||
|
|
||||||
|
#TODO: Do something useful like alert user
|
||||||
|
#print "No hands returned by graph query"
|
||||||
|
else:
|
||||||
|
self.ax.set_title(_("Tournament Results"))
|
||||||
|
|
||||||
|
#Draw plot
|
||||||
|
self.ax.plot(green, color='green', label=_('Tournaments: %d\nProfit: $%.2f') %(len(green), green[-1]))
|
||||||
|
#self.ax.plot(blue, color='blue', label=_('Showdown: $%.2f') %(blue[-1]))
|
||||||
|
#self.ax.plot(red, color='red', label=_('Non-showdown: $%.2f') %(red[-1]))
|
||||||
|
if sys.version[0:3] == '2.5':
|
||||||
|
self.ax.legend(loc='upper left', shadow=True, prop=FontProperties(size='smaller'))
|
||||||
|
else:
|
||||||
|
self.ax.legend(loc='upper left', fancybox=True, shadow=True, prop=FontProperties(size='smaller'))
|
||||||
|
|
||||||
|
self.graphBox.add(self.canvas)
|
||||||
|
self.canvas.show()
|
||||||
|
self.canvas.draw()
|
||||||
|
#self.exportButton.set_sensitive(True)
|
||||||
|
except:
|
||||||
|
err = traceback.extract_tb(sys.exc_info()[2])[-1]
|
||||||
|
print _("***Error: ")+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
|
||||||
|
|
||||||
|
#end of def showClicked
|
||||||
|
|
||||||
|
def getData(self, names, sites):
|
||||||
|
tmp = self.sql.query['tourneyResults']
|
||||||
|
print "DEBUG: getData"
|
||||||
|
start_date, end_date = self.filters.getDates()
|
||||||
|
|
||||||
|
#Buggered if I can find a way to do this 'nicely' take a list of integers and longs
|
||||||
|
# and turn it into a tuple readale by sql.
|
||||||
|
# [5L] into (5) not (5,) and [5L, 2829L] into (5, 2829)
|
||||||
|
nametest = str(tuple(names))
|
||||||
|
sitetest = str(tuple(sites))
|
||||||
|
|
||||||
|
#Must be a nicer way to deal with tuples of size 1 ie. (2,) - which makes sql barf
|
||||||
|
tmp = tmp.replace("<player_test>", nametest)
|
||||||
|
tmp = tmp.replace("<site_test>", sitetest)
|
||||||
|
tmp = tmp.replace("<startdate_test>", start_date)
|
||||||
|
tmp = tmp.replace("<enddate_test>", end_date)
|
||||||
|
tmp = tmp.replace(",)", ")")
|
||||||
|
|
||||||
|
print "DEBUG: sql query:"
|
||||||
|
print tmp
|
||||||
|
self.db.cursor.execute(tmp)
|
||||||
|
#returns (HandId,Winnings,Costs,Profit)
|
||||||
|
winnings = self.db.cursor.fetchall()
|
||||||
|
self.db.rollback()
|
||||||
|
|
||||||
|
if len(winnings) == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
green = map(lambda x:float(x[0]), winnings)
|
||||||
|
#blue = map(lambda x: float(x[1]) if x[2] == True else 0.0, winnings)
|
||||||
|
#red = map(lambda x: float(x[1]) if x[2] == False else 0.0, winnings)
|
||||||
|
greenline = cumsum(green)
|
||||||
|
#blueline = cumsum(blue)
|
||||||
|
#redline = cumsum(red)
|
||||||
|
return (greenline/100)
|
||||||
|
|
||||||
|
def exportGraph (self, widget, data):
|
||||||
|
if self.fig is None:
|
||||||
|
return # Might want to disable export button until something has been generated.
|
||||||
|
|
||||||
|
dia_chooser = gtk.FileChooserDialog(title=_("Please choose the directory you wish to export to:"),
|
||||||
|
action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
|
||||||
|
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OK,gtk.RESPONSE_OK))
|
||||||
|
dia_chooser.set_destroy_with_parent(True)
|
||||||
|
dia_chooser.set_transient_for(self.parent)
|
||||||
|
try:
|
||||||
|
dia_chooser.set_filename(self.exportFile) # use previously chosen export path as default
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
response = dia_chooser.run()
|
||||||
|
|
||||||
|
if response <> gtk.RESPONSE_OK:
|
||||||
|
print _('Closed, no graph exported')
|
||||||
|
dia_chooser.destroy()
|
||||||
|
return
|
||||||
|
|
||||||
|
# generate a unique filename for export
|
||||||
|
now = datetime.now()
|
||||||
|
now_formatted = now.strftime("%Y%m%d%H%M%S")
|
||||||
|
self.exportFile = dia_chooser.get_filename() + "/fpdb" + now_formatted + ".png"
|
||||||
|
dia_chooser.destroy()
|
||||||
|
|
||||||
|
#print "DEBUG: self.exportFile = %s" %(self.exportFile)
|
||||||
|
self.fig.savefig(self.exportFile, format="png")
|
||||||
|
|
||||||
|
#display info box to confirm graph created
|
||||||
|
diainfo = gtk.MessageDialog(parent=self.parent,
|
||||||
|
flags=gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||||
|
type=gtk.MESSAGE_INFO,
|
||||||
|
buttons=gtk.BUTTONS_OK,
|
||||||
|
message_format=_("Graph created"))
|
||||||
|
diainfo.format_secondary_text(self.exportFile)
|
||||||
|
diainfo.run()
|
||||||
|
diainfo.destroy()
|
||||||
|
|
||||||
|
#end of def exportGraph
|
|
@ -3027,6 +3027,9 @@ class Sql:
|
||||||
#elif db_server == 'sqlite':
|
#elif db_server == 'sqlite':
|
||||||
# self.query['playerStatsByPosition'] = """ """
|
# self.query['playerStatsByPosition'] = """ """
|
||||||
|
|
||||||
|
####################################
|
||||||
|
# Cash Game Graph query
|
||||||
|
####################################
|
||||||
self.query['getRingProfitAllHandsPlayerIdSite'] = """
|
self.query['getRingProfitAllHandsPlayerIdSite'] = """
|
||||||
SELECT hp.handId, hp.totalProfit, hp.sawShowdown
|
SELECT hp.handId, hp.totalProfit, hp.sawShowdown
|
||||||
FROM HandsPlayers hp
|
FROM HandsPlayers hp
|
||||||
|
@ -3043,6 +3046,28 @@ class Sql:
|
||||||
GROUP BY h.startTime, hp.handId, hp.sawShowdown, hp.totalProfit
|
GROUP BY h.startTime, hp.handId, hp.sawShowdown, hp.totalProfit
|
||||||
ORDER BY h.startTime"""
|
ORDER BY h.startTime"""
|
||||||
|
|
||||||
|
####################################
|
||||||
|
# Tourney Results query
|
||||||
|
####################################
|
||||||
|
self.query['tourneyResults'] = """
|
||||||
|
SELECT (tp.winnings - tt.buyIn - tt.fee) as profit, tp.koCount, tp.rebuyCount, tp.addOnCount, tt.buyIn, tt.fee
|
||||||
|
FROM TourneysPlayers tp
|
||||||
|
INNER JOIN Players pl ON (pl.id = tp.playerId)
|
||||||
|
INNER JOIN Tourneys t ON (t.id = tp.tourneyId)
|
||||||
|
INNER JOIN TourneyTypes tt ON (tt.id = t.tourneyTypeId)
|
||||||
|
WHERE pl.id in <player_test>
|
||||||
|
AND pl.siteId in <site_test>
|
||||||
|
AND t.startTime > '<startdate_test>'
|
||||||
|
AND t.startTime < '<enddate_test>'
|
||||||
|
GROUP BY t.startTime, tp.tourneyId, tp.winningsCurrency,
|
||||||
|
tp.winnings, tp.koCount,
|
||||||
|
tp.rebuyCount, tp.addOnCount,
|
||||||
|
tt.buyIn, tt.fee
|
||||||
|
ORDER BY t.startTime"""
|
||||||
|
|
||||||
|
#AND gt.type = 'ring'
|
||||||
|
#<limit_test>
|
||||||
|
#<game_test>
|
||||||
|
|
||||||
####################################
|
####################################
|
||||||
# Session stats query
|
# Session stats query
|
||||||
|
|
|
@ -122,6 +122,7 @@ import GuiTourneyViewer
|
||||||
import GuiPositionalStats
|
import GuiPositionalStats
|
||||||
import GuiAutoImport
|
import GuiAutoImport
|
||||||
import GuiGraphViewer
|
import GuiGraphViewer
|
||||||
|
import GuiTourneyGraphViewer
|
||||||
import GuiSessionViewer
|
import GuiSessionViewer
|
||||||
import SQL
|
import SQL
|
||||||
import Database
|
import Database
|
||||||
|
@ -778,6 +779,7 @@ class fpdb:
|
||||||
<menuitem action="autoimp"/>
|
<menuitem action="autoimp"/>
|
||||||
<menuitem action="hudConfigurator"/>
|
<menuitem action="hudConfigurator"/>
|
||||||
<menuitem action="graphs"/>
|
<menuitem action="graphs"/>
|
||||||
|
<menuitem action="tourneygraphs"/>
|
||||||
<menuitem action="ringplayerstats"/>
|
<menuitem action="ringplayerstats"/>
|
||||||
<menuitem action="tourneyplayerstats"/>
|
<menuitem action="tourneyplayerstats"/>
|
||||||
<menuitem action="tourneyviewer"/>
|
<menuitem action="tourneyviewer"/>
|
||||||
|
@ -817,6 +819,7 @@ class fpdb:
|
||||||
('autoimp', None, _('_Auto Import and HUD'), _('<control>A'), 'Auto Import and HUD', self.tab_auto_import),
|
('autoimp', None, _('_Auto Import and HUD'), _('<control>A'), 'Auto Import and HUD', self.tab_auto_import),
|
||||||
('hudConfigurator', None, _('_HUD Configurator'), _('<control>H'), 'HUD Configurator', self.diaHudConfigurator),
|
('hudConfigurator', None, _('_HUD Configurator'), _('<control>H'), 'HUD Configurator', self.diaHudConfigurator),
|
||||||
('graphs', None, _('_Graphs'), _('<control>G'), 'Graphs', self.tabGraphViewer),
|
('graphs', None, _('_Graphs'), _('<control>G'), 'Graphs', self.tabGraphViewer),
|
||||||
|
('tourneygraphs', None, _('Tourney Graphs'), None, 'TourneyGraphs', self.tabTourneyGraphViewer),
|
||||||
('ringplayerstats', None, _('Ring _Player Stats (tabulated view, not on pgsql)'), _('<control>P'), 'Ring Player Stats (tabulated view)', self.tab_ring_player_stats),
|
('ringplayerstats', None, _('Ring _Player Stats (tabulated view, not on pgsql)'), _('<control>P'), 'Ring Player Stats (tabulated view)', self.tab_ring_player_stats),
|
||||||
('tourneyplayerstats', None, _('_Tourney Player Stats (tabulated view, not on pgsql)'), _('<control>T'), 'Tourney Player Stats (tabulated view, mysql only)', self.tab_tourney_player_stats),
|
('tourneyplayerstats', None, _('_Tourney Player Stats (tabulated view, not on pgsql)'), _('<control>T'), 'Tourney Player Stats (tabulated view, mysql only)', self.tab_tourney_player_stats),
|
||||||
('tourneyviewer', None, _('Tourney _Viewer'), None, 'Tourney Viewer)', self.tab_tourney_viewer_stats),
|
('tourneyviewer', None, _('Tourney _Viewer'), None, 'Tourney Viewer)', self.tab_tourney_viewer_stats),
|
||||||
|
@ -1066,6 +1069,13 @@ You can find the full license texts in agpl-3.0.txt, gpl-2.0.txt, gpl-3.0.txt an
|
||||||
gv_tab = new_gv_thread.get_vbox()
|
gv_tab = new_gv_thread.get_vbox()
|
||||||
self.add_and_display_tab(gv_tab, _("Graphs"))
|
self.add_and_display_tab(gv_tab, _("Graphs"))
|
||||||
|
|
||||||
|
def tabTourneyGraphViewer(self, widget, data=None):
|
||||||
|
"""opens a graph viewer tab"""
|
||||||
|
new_gv_thread = GuiTourneyGraphViewer.GuiTourneyGraphViewer(self.sql, self.config, self.window)
|
||||||
|
self.threads.append(new_gv_thread)
|
||||||
|
gv_tab = new_gv_thread.get_vbox()
|
||||||
|
self.add_and_display_tab(gv_tab, _("Tourney Graphs"))
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# no more than 1 process can this lock at a time:
|
# no more than 1 process can this lock at a time:
|
||||||
self.lock = interlocks.InterProcessLock(name="fpdb_global_lock")
|
self.lock = interlocks.InterProcessLock(name="fpdb_global_lock")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user