I have a QTableWidget with which I would like to update from a QLinEdit embedded into a context menu. Now, in the QLinEdit a server name is entered, when the key is pressed the program scans MySQL database to see if the server name is in it, if it is, it updates the QTableWidgetwith the data from the server name table, if it is not found, it gives an error messageBox.
What I can not do is connect the context menu QLinEdit to update the QTableWidget.
connecting QTableWidget to context menu:
self.table1.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.table1.customContextMenuRequested.connect(self.handleHeaderMenu)
contextmenu:
def handleHeaderMenu(self, pos):
self.custom_choice = QtGui.QLineEdit()
self.menu = QtGui.QMenu()
self.custom_choice.setPlaceholderText("Server")
self.wac = QtGui.QWidgetAction(self.menu)
self.wac.setDefaultWidget(self.custom_choice)
self.menu.setStyleSheet("QMenu::item {background-color: #264F7D;color: white; font-weight:bold;}")
self.menu.addAction("Choose Server to Monitor:")
self.menu.addSeparator()
self.actionJuliet = self.menu.addAction('Juliet')
self.actionJulietleft = self.menu.addAction('JulietLeft')
self.actionPong = self.menu.addAction('Pong')
self.actionHulk = self.menu.addAction('Hulk')
self.actionCustom = self.menu.addAction(self.wac)
action = self.menu.exec_(QtGui.QCursor.pos())
self.connect(self.custom_choice, QtCore.SIGNAL("returnPressed()"),self.refreshdata)
Data fetcher/scanner:
def get_data(self):
self.tx = self.custom_choice.text()
self.model.execute("show TABLES;")
table_array = []
table_names = self.model.fetchall()
for lines in table_names:
lines = str(lines)
lines = lines.strip("()""''"",")
table_array.append(lines)
if any("%s" % self.tx in s for s in table_array):
table_name = self.tx
self.model.execute("""SELECT computer_name
FROM %s""" % (table_name))
new_user_name = self.model.fetchall()
print new_user_name,table_name
self.model.execute("""SELECT idle_time
FROM %s""" % (table_name))
new_idle = self.model.fetchall()
self.model.execute("""SELECT files_opened
FROM %s""" % (table_name))
new_files = self.model.fetchall()
self.model.execute("""SELECT active_time
FROM %s""" % (table_name))
new_active = self.model.fetchall()
self.model.execute("""SELECT session_type
FROM %s""" % (table_name))
new_session = self.model.fetchall()
self.model.execute("""SELECT cpu
FROM %s""" % (table_name))
new_cpu_load = self.model.fetchall()
self.model.execute("""SELECT avg_disk_queue
FROM %s""" % (table_name))
new_disk_queue_load = self.model.fetchall()
new_data_user = [item0[0] for item0 in new_user_name]
new_data_idle = [item1[0] for item1 in new_idle]
new_data_files = [item2[0] for item2 in new_files]
new_data_active = [item3[0] for item3 in new_active]
new_data_session = [item4[0] for item4 in new_session]
new_data_cpu_load = [item5[0] for item5 in new_cpu_load]
new_data_disk_queue_load = [item6[0] for item6 in new_disk_queue_load]
self.lista = new_data_user
self.listb = new_data_disk_queue_load
self.listc = new_data_cpu_load
self.listd = new_data_active
self.liste = new_data_files
self.listf = new_data_session
self.listg = new_data_idle
self.mystruct2 = {'A':self.lista, 'B':self.listb, 'C':self.listc,'E':self.liste,'D':self.listd,'F':self.listf,'G':self.listg}
The design of your handleHeaderMenu is a bit off. One of the main issues with the way it is currently structured is that you connect a signal to the QLineEdit after the popup menu has already finished. So you would miss that signal.
action = self.menu.exec_(QtGui.QCursor.pos())
self.connect(self.custom_choice,
QtCore.SIGNAL("returnPressed()"),
self.refreshdata)
QMenu.exec_() is a blocking call. It starts the event loop for the menu and waits for it to finish. Once it closes and returns the QAction that was selected, you then make a connection. I will get into a correction for this after my next point...
The menu you are building from scratch each time doesn't have to be saved to member attributes and used externally. There are two ways to go about doing a custom popup menu. If its primarily static, then you can build it once in your class init, or make it it's own class, and then just reuse the instance. Or in your case, you could build it up each time which is fine. But instead of relying on persistant references to the components of the menu and using a signal, why not just build it temporarily, and explicitly handle the results?
def handleHeaderMenu(self, pos):
menu = QtGui.QMenu()
menu.setStyleSheet("""
QMenu::item {
background-color: #264F7D;
color: white;
font-weight:bold;}
""")
text = menu.addAction("Choose Server to Monitor:")
text.setEnabled(False)
menu.addSeparator()
actionJuliet = menu.addAction('Juliet')
actionJulietleft = menu.addAction('JulietLeft')
actionPong = menu.addAction('Pong')
actionHulk = menu.addAction('Hulk')
wac = QtGui.QWidgetAction(menu)
custom_choice = QtGui.QLineEdit()
custom_choice.setPlaceholderText("Server")
wac.setDefaultWidget(custom_choice)
menu.addAction(wac)
menu.setActiveAction(wac)
custom_choice.returnPressed.connect(wac.trigger)
action = menu.exec_(QtGui.QCursor.pos())
if action:
if action == wac:
self.tx = str(custom_choice.text()).strip()
else:
self.tx = str(action.text())
self.refreshdata()
def refreshdata(self):
print self.tx
Here we just create a bunch of temp widgets for the menu that will get garbage collected. After showing the menu, we check the returned action and then manually set our table attribute, and call refresh. Also, we needed to set the signal from the custom QLineEdit to trigger its widget action internally.
Lastly, is it really necessary to do 8 sql queries and a whole bunch of data reorganizing every time you want to load this data? This could be highly simplified:
def get_data(self):
table_check = """
SELECT table_name FROM information_schema.tables
WHERE table_schema = %s AND table_name = %s
"""
table_name = self.tx
count = self.model.execute(table_check, (self.theDatabaseName, table_name))
if not count:
# warn the user that the table name does not exist
warn_user_of_bad_table()
return
sql = """
SELECT
computer_name, idle_time, files_opened,
active_time, session_type, cpu, avg_disk_queue
FROM %s
""" % table_name
count = self.model.execute(sql)
if not count:
warn_database_error()
return
results = self.model.fetchall()
user, idle , files, active, session, cpu, disk = zip(*results)
self.lista = user
self.listb = disk
self.listc = cpu
self.listd = active
self.liste = files
self.listf = session
self.listg = idle
self.mystruct2 = {
'A' : self.lista,
'B' : self.listb,
'C' : self.listc,
'E' : self.liste,
'D' : self.listd,
'F' : self.listf,
'G' : self.listg
}
You only need two queries here. The first really simple one to check if the table exists, by using the scheme, instead of parsing through a big SHOW TABLES output. And the second which gets all your data in one query (as a bunch of rows, and then uses zip to regroup them into columns.
Related
OS: ubuntu 20.04
IDE: pyCharm community Build #PC-223.8214.51, built on December 20, 2022
Language: python 3.10.6
I'm using kivy to develop a simple app that queries an sqlite3 database for data written in Arabic. Now, because kivy doesn't natively support Arabic, I had to use both BIDI and arabic-reshaper so I can allow the user to type in proper Arabic.
The database is created from a .csv file, which in turn created by the LibreOffice calc also on Ubuntu 20.04. I have used pandas on terminal to create my database as follows:
users = pd.read_csv('numberDataBaseCSV.csv')
users.to_sql('users', conn, if_exists='replace', index = False)
First, I had to override the TextInput.insert_text as follows:
class Ar_text(TextInput):
max_chars = NumericProperty(20) # maximum character allowed
str = StringProperty()
def __init__(self, **kwargs):
super(Ar_text, self).__init__(**kwargs)
self.text = bidi.algorithm.get_display(arabic_reshaper.reshape("اطبع شيئاً"))
def insert_text(self, substring, from_undo=False):
if not from_undo and (len(self.text) + len(substring) > self.max_chars):
return
self.str = self.str+substring
self.text = bidi.algorithm.get_display(arabic_reshaper.reshape(self.str))
substring = ""
super(Ar_text, self).insert_text(substring, from_undo)
def do_backspace(self, from_undo=False, mode='bkspc'):
self.str = self.str[0:len(self.str)-1]
self.text = bidi.algorithm.get_display(arabic_reshaper.reshape(self.str))
Then I built the app as usual:
class TestApp(kivy.app.App):
def build(self):
def create_connection(db_file):
""" create a database connection to the SQLite database
specified by the db_file
:param db_file: database file
:return: Connection object or None
"""
conn = None
try:
conn = sqlite3.connect(db_file)
except sqlite3.Error as e:
print(e)
return conn
self.dbConnect = create_connection("numberData.db")
self.number_cursor = self.dbConnect.cursor()
reshaped_text = arabic_reshaper.reshape("بحث")
bidi_text = bidi.algorithm.get_display(reshaped_text)
self.circuit_number_label = kivy.uix.label.Label(text=bidi_text, font_name="janna-lt-bold/Janna LT Bold/Janna LT Bold.ttf")
self.nameTextField = Ar_text(text=bidi_text, font_name="janna-lt-bold/Janna LT Bold/Janna LT Bold.ttf")
self.searchButton = kivy.uix.button.Button(text=bidi_text, font_name="janna-lt-bold/Janna LT Bold/Janna LT Bold.ttf")
self.searchButton.bind(on_press = self.search_number_by_name)
boxLayout = kivy.uix.boxlayout.BoxLayout(orientation="vertical")
boxLayout.add_widget(self.circuit_number_label)
boxLayout.add_widget(self.nameTextField)
boxLayout.add_widget(self.searchButton)
return boxLayout
ِAnd this is the callback that's triggered when I click the button:
def search_number_by_name(self, event):
reshaped_text = bidi.algorithm.get_display(self.nameTextField.text)
print(reshaped_text)
print("select الرقم,الدارة from numbers_table where الاسم = '" + reshaped_text.strip() + "'")
self.number_cursor.execute("select الرقم,الدارة from numbers_table where الاسم = '" + reshaped_text.strip() + "'")
rows = self.number_cursor.fetchall()
for row in rows:
print(row)
And then of course running the app:
testApp = TestApp()
testApp.run()
Even though the data is correct and I should get the queried data from the database, nothing seems to be happening. Not even an error message.
when I hard code the data in the where clause:
quereied_data = "خالد"
and then build the query:
self.number_cursor.execute("select الرقم,الدارة from numbers_table where الاسم = '" + queried_data + "'")
I get the expected results from the database, only when I retrieve the data from the TextInput I get nothing.
I've tried to "undo" the reshaping process with the code snippet:
reshaped_text = bidi.algorithm.get_display(self.nameTextField.text)
But that also didn't work, I'm not sure where the problem is since I can't get any error messages. I'd appreciate the help.
As it turns out, there's a property in the TextInput class: str, so in order to retrieve the data and use in a query you need to do the following:
reshaped_text = self.nameTextField.str
Then use it as usual in the built query:
self.number_cursor.execute("select الرقم,الدارة from numbers_table where الاسم = '" + reshaped_text.strip() + "'")
rows = self.number_cursor.fetchall()
for row in rows:
print(row)
I'm trying to make a products' management app with tkinter and sqlite 3.
I want to show the products in labels. It works correctly, but I also want to delete every label when I add an item, and then recreate new labels. This way, the list updates when adding a new item.
Otherwise the user needs to restart the app to see the new item. Here's the code:
from tkinter import *
import sqlite3
# table name = items
conn = sqlite3.connect("productManagement.db")
cursor = conn.cursor()
itemsSearch = cursor.execute("SELECT rowid, * FROM items")
itemsFetch = itemsSearch.fetchall()
window = Tk()
window.geometry("800x600")
window.config(bg="#9BB7D4")
#FUNCTIONS
itemFrame = Frame(bg="#8a8a8a",width=200,height=200)
frameTitle = Label(itemFrame,text="Products:",bg="#8a8a8a",font=("Arial Black",12))
frameTitle.pack()
def createProduct():
global itemsFetch
name = nameEntry.get()
price = priceEntry.get()
quantity = quantityEntry.get()
number = numberEntry.get()
cursor.execute(f"INSERT INTO items VALUES ('{name}',{int(price)},{int(quantity)},
{int(number)})")
conn.commit()
itemsSearch = cursor.execute("SELECT rowid, * FROM items")
itemsFetch = itemsSearch.fetchall()
#the problem is here: i create new label for every item but the old ones doesn't
dissapear, i want to delete all the labels of the items existing and create new ones
showProducts()
def showProducts():
global itemStats
for item in itemsFetch:
itemStats = Label(itemFrame,bg="#8a8a8a",font=("Italic",12),
text=f"Name: {item[1]} Price: {int(item[2])}€ Quantity: {int(item[3])}
Item no: {int(item[4])}")
deleteBtn = Button(itemFrame,text="Delete")
itemStats.pack()
showProducts()
#GUI
title = Label(text="Product Managment System",font=("Arial Black",24),bg="#9BB7D4")
nameText = Label(text="Name:",bg="#9BB7D4",font=("Italic",12))
nameEntry = Entry(font=("Italic",12))
priceText = Label(text="Price:",bg="#9BB7D4",font=("Italic",12))
priceEntry = Entry(font=("Italic",12))
quantityText = Label(text="Quantity:",bg="#9BB7D4",font=("Italic",12))
quantityEntry = Entry(font=("Italic",12))
numberText = Label(text="Product Number:",bg="#9BB7D4",font=("Italic",12))
numberEntry = Entry(font=("Italic",12))
createProductBtn = Button(text="Create item",command=createProduct)
#PACKS
title.pack(pady=20)
nameText.place(x=140,y=140)
nameEntry.place(x=190,y=140)
priceText.place(x=340,y=140)
priceEntry.place(x=387,y=140)
quantityText.place(x=125,y=200)
quantityEntry.place(x=190,y=200)
numberText.place(x=340,y=200)
numberEntry.place(x=463,y=200)
createProductBtn.place(x=190,y=260)
itemFrame.place(x=190,y=300)
conn.commit()
window.mainloop()
conn.close()
I have written a webapp which queries data from a database based on some inputs (start/end datetimes, machine id and parameter id) and shows it in a bokeh figure:
As you can see so far it works as intended but I have some plans to extend this app further:
Allow data from different batches (with different start/end timestamps) to be loaded into the same graph.
Perform some statistical analysis of the different batches, e.g. averages, standard deviations, control limits, etc.
Get live streaming updates of parameters for different machines and/or parameters.
So I am now at the point where the app starts to become more complex and I want to refactor the code into a maintainable and extensible format. Currently, the code is written procedurally and i would like to move to a MVC-like model to separate the data querying from the bokeh visualizations and statistical computations but I am unsure how to approach this best.
How can i refactor my code best?
import logging
import pymssql, pandas
from dateutil import parser
from datetime import datetime, timedelta
from bokeh import layouts, models, plotting, settings
from bokeh.models import widgets
SETTINGS = {
'server': '',
'user': '',
'password': '',
'database': ''
}
def get_timestamps(datetimes):
""" DB timestamps are in milliseconds """
return [int(dt.timestamp()*1000) for dt in datetimes]
def get_db_names(timestamps):
logging.debug('Started getting DB names ...')
query = """
SELECT
[DBName]
FROM [tblDBNames]
WHERE {timestamp_ranges}
""".format(
timestamp_ranges = ' OR '.join([f'({timestamp} BETWEEN [LStart] AND [LStop])' for timestamp in timestamps])
)
logging.debug(query)
db_names = []
with pymssql.connect(**SETTINGS) as conn:
with conn.cursor(as_dict=True) as cursor:
cursor.execute(query)
for row in cursor:
db_names.append(row['DBName'])
#logging.debug(db_names)
logging.debug('Finished getting DB names')
return list(set(db_names))
def get_machines():
logging.debug('Started getting machines ...')
query = """
SELECT
CONVERT(VARCHAR(2),[ID]) AS [ID],
[Name]
FROM [tblMaschinen]
WHERE NOT [Name] = 'TestLine4'
ORDER BY [Name]
"""
logging.debug(query)
with pymssql.connect(**SETTINGS) as conn:
with conn.cursor(as_dict=False) as cursor:
cursor.execute(query)
data = cursor.fetchall()
#logging.debug(data)
logging.debug('Finished getting machines')
return data
def get_parameters(machine_id, parameters):
logging.debug('Started getting process parameteres ...')
query = """
SELECT
CONVERT(VARCHAR(4), TrendConfig.ID) AS [ID],
TrendConfig_Text.description AS [Description]
FROM [TrendConfig]
INNER JOIN TrendConfig_Text
ON TrendConfig.ID = TrendConfig_Text.ID
WHERE (TrendConfig_Text.languageText_KEY = 'nl')
AND TrendConfig.MaschinenID = {machine_id}
AND TrendConfig_Text.description IN ('{parameters}')
ORDER BY TrendConfig_Text.description
""".format(
machine_id = machine_id,
parameters = "', '".join(parameters)
)
logging.debug(query)
with pymssql.connect(**SETTINGS) as conn:
with conn.cursor(as_dict=False) as cursor:
cursor.execute(query)
data = cursor.fetchall()
#logging.debug(data)
logging.debug('Finished getting process parameters')
return data
def get_process_data(query):
logging.debug('Started getting process data ...')
with pymssql.connect(**SETTINGS) as conn:
return pandas.read_sql(query, conn, parse_dates={'LTimestamp': 'ms'}, index_col='LTimestamp')
logging.debug('Finished getting process data')
batches = widgets.Slider(start=1, end=10, value=1, step=1, title="Batches")
now, min_date = datetime.now(), datetime.fromtimestamp(1316995200)
date_start = widgets.DatePicker(title="Start date:", value=str(now.date()), min_date=str(min_date), max_date=str(now.date()))
time_start = widgets.TextInput(title="Start time:", value=str((now-timedelta(hours=1)).replace(microsecond=0).time()))
start_row = layouts.Row(children=[date_start, time_start], width = 300)
date_end = widgets.DatePicker(title="End date:", value=str(now.date()), min_date=str(min_date), max_date=str(now.date()))
time_end = widgets.TextInput(title="End time:", value=str(now.replace(microsecond=0).time()))
end_row = layouts.Row(children=[date_end, time_end], width = 300)
datetimes = layouts.Column(children=[start_row, end_row])
## Machine list
machines = get_machines()
def select_machine_cb(attr, old, new):
logging.debug(f'Changed machine ID: old={old}, new={new}')
parameters = get_parameters(select_machine.value, default_params)
select_parameters.options = parameters
select_parameters.value = [parameters[0][0]]
select_machine = widgets.Select(
options = machines,
value = machines[0][0],
title = 'Machine:'
)
select_machine.on_change('value', select_machine_cb)
## Parameters list
default_params = [
'Debiet acuteel',
'Extruder energie',
'Extruder kWh/kg',
'Gewicht bunker',
'RPM Extruder acuteel',
'Temperatuur Kop'
]
parameters = get_parameters(select_machine.value, default_params)
select_parameters = widgets.MultiSelect(
options = parameters,
value = [parameters[0][0]],
title = 'Parameter:'
)
def btn_update_cb(arg):
logging.debug('btn_update clicked')
datetime_start = parser.parse(f'{date_start.value} {time_start.value}')
datetime_end = parser.parse(f'{date_end.value} {time_end.value}')
datetimes = [datetime_start, datetime_end]
timestamps = get_timestamps(datetimes)
db_names = get_db_names(timestamps)
machine_id = select_machine.value
parameter_ids = select_parameters.value
query = """
SELECT
[LTimestamp],
[TrendConfigID],
[Text],
[Value]
FROM ({derived_table}) [Trend]
LEFT JOIN [TrendConfig] AS [TrendConfig]
ON [Trend].[TrendConfigID] = [TrendConfig].[ID]
WHERE [LTimestamp] BETWEEN {timestamp_range}
AND [Trend].[TrendConfigID] IN ({id_range})
""".format(
derived_table = ' UNION ALL '.join([f'SELECT * FROM [{db_name}].[dbo].[Trend_{machine_id}]' for db_name in db_names]),
timestamp_range = ' AND '.join(map(str,timestamps)),
id_range = ' ,'.join(parameter_ids)
)
logging.debug(query)
df = get_process_data(query)
ds = models.ColumnDataSource(df)
plot.renderers = [] # clear plot
#view = models.CDSView(source=ds, filters=[models.GroupFilter(column_name='TrendConfigID', group='')])
#plot = plotting.figure(plot_width=600, plot_height=300, x_axis_type='datetime')
plot.line(x='LTimestamp', y='Value', source=ds, name='line')
btn_update = widgets.Button(
label="Update",
button_type="primary",
width = 150
)
btn_update.on_click(btn_update_cb)
btn_row = layouts.Row(children=[btn_update])
column = layouts.Column(children=[batches, datetimes, select_machine, select_parameters, btn_row], width = 300)
plot = plotting.figure(plot_width=600, plot_height=300, x_axis_type='datetime')
row = layouts.Row(children=[column, layouts.Spacer(width=20), plot])
tab1 = models.Panel(child=row, title="Viewer")
tab2 = models.Panel(child=layouts.Spacer(), title="Settings")
tabs = models.Tabs(tabs=[tab1, tab2])
plotting.curdoc().add_root(tabs)
I would recommend OOP (Object Oriented Programming).
To do this you would need to:
Come up with a standardized object that is being graphed. For
example (Point:{value: x, startTime: a, endTime: y})
Extract obtaining the object (or list of objects) into a separate
class/file (example found here: http://www.qtrac.eu/pyclassmulti.html)
Come up with a standardized interface. For
example, the interface could be called 'Batch' and could have a
method 'obtainPoints' that returns a list of 'Point' objects defined
in step 1.
Now, you can create multiple Batch Implementations that implement the interface and the main class could call the obtainPoints method on the separate implementations and graph them. In the end you would have 1 interface (Batch) X implementations of Batch (ie. SQLDatabaseBatch, LDAPBatch, etc...) and a main class that utilizes all of these Batch implementation(s) and creates a graph.
im trying to search a GTK 3 treestore for a string. The treestore has 4 columns,and is for a treeview widget that has callapsible nodes. im creating the nodes with this function:
def AddItem(self,ParentIter,txt,datapath='',projName=Project):
self.store = self.builder.get_object('theTreeStore')
NodeId = secrets.token_hex(8)
if ParentIter == None:
ParentNodeId = ''
else:
ParentNodeId = self.store.get_value(ParentIter, 2)
treeEntry = ['%s' %ParentNodeId,'%s' %txt,'%s' %NodeId,'%s' %datapath]
node = self.store.append(ParentIter, treeEntry) <<<<<<<<<<<<<
self.view = self.builder.get_object('Tree')
self.view.set_model(self.store)
# table nodes(tParentNodeID ,tNodeTxt ,tNodeID ,tDataPath );
sql = "INSERT INTO %s (tParentNodeID ,tNodeTxt ,tNodeID ,tDataPath ) VALUES ('%s','%s','%s','%s')" %(projName,ParentNodeId,txt,NodeId,datapath)
self.cursor.execute(sql)
self.mariadb_connection.commit()
for x in self.cursor:
print(x)
return(node)
as you can see the data in the tree is nested in its parent.
now i need to somehow search the treestore for a row that contains a certain NodeId string. Ive read the gtk docs over and over but i cant quite figure out what to do. im guessing i need to use following methods:
store.get_iter()
store.iter_children()
but idk everything i try only returns the root nodes no children.
i basically want a search function that will recursively search each node and its children,and their children for a string. something like this:
def GetRowbyNodeID(nodeid):
for row in treestore:
if row[1]==nodeid:
return(row)
for children in row:
if children[1] == nodeid
return(children)
The code is in multiple files, i can post any functions relevant if needed.
GtkTreeStore implements GtkTreeModel interface. Thus you can use the following methods:
iter = store.get_iter() to obtain an iterator
chld_iter = iter.get_children()to obtain an iterator over children elements (please note, it's an iter's method!)
I'd also recommend reading this tutorial. "The Model" section contains all you need on iterating over the model (spoiler: search for print_tree_store)
Got it all working. thanks again. im posting the relevant code just in case anyone else could use it.
def SearchTreeRows(self,store, treeiter, searchstr):
print("\nsearch>%s"%searchstr)
while treeiter != None:
if store[treeiter][2] ==searchstr:
print("found in:%s"%str(store[treeiter][:]))
return(treeiter)
break
print("searched:%s"%str(store[treeiter][:]))
if store.iter_has_child(treeiter):
childiter = store.iter_children(treeiter)
ret = self.SearchTreeRows(store, childiter, searchstr)
if ret is not None:
return ret
treeiter = store.iter_next(treeiter)
def NodeId2Tree(self,nodeid):
self.store = self.builder.get_object('theTreeStore')
rootiter = self.store.get_iter_first()
row = self.SearchTreeRows(self.store, rootiter,nodeid)
return(row)
def LoadProject(self):
global Project
global ProjSel
sql = "SHOW TABLES"
self.cursor.execute(sql)
tbls = []
for x in self.cursor:
tbls.append(x)
diag = self.builder.get_object('ProjectChooser')
self.combo = Gtk.ComboBox()
ls =Gtk.ListStore(str)
for tble in tbls:
strg ="%s" %tble
ls.append(tble)
self.combo.set_model(ls)
cellr = Gtk.CellRendererText()
self.combo.pack_start(cellr,True)
self.combo.add_attribute(cellr, 'text', 0)
diag.vbox.pack_start(self.combo, True, True, 5)
diag.show_all()
response = diag.run()
self.combo.destroy()
print(ProjSel)
Project = ProjSel
ProjSel = ''
view = self.builder.get_object('Tree')
self.store.clear()
view.set_model(self.store)
sql = "SELECT tParentNodeId,tNodeTxt,tNodeId FROM %s"%(Project)
self.cursor.execute(sql)
for x in self.cursor:
parid = x[0]
nodtxt = x[1]
nodid =x[2]
if parid == '':
treeEntry = ['%s' %parid, '%s' %nodtxt, '%s' %nodid, '']
node = self.store.append(None, treeEntry) #root nodes
else:
treeEntry = ['%s' %parid, '%s' %nodtxt, '%s' %nodid, '']
n2id = self.NodeId2Tree(parid)
node = self.store.append(n2id, treeEntry)
print("got return:%s For:%s"%(n2id,treeEntry[0]))
view.set_model(self.store)
#select * where parentid == none >> get root nodes ???? or parse line by line
I am trying to automate this scenario. I have 2 .sql files (add1.sql and add2.sql) which has 1 insert script each.
My goal is to write one record to table1 by executing lines from add1.sql and one record to cm.cl by executing lines from add2.sql, waiting for about 5 mins so a backend service runs. This service writes from DB1 to DB2. I then connect to DB2 to see if the record from DB1 matches what was written to DB2. Depending no the results, an email is sent.
Below is my code. Everything works just fine except that it writes twice to DB1. So, basically 4 records are inserted instead of 2. Any idea why it writes 4 records?
import pypyodbc as pyodbc
import smtplib
sender = 'abc#abc.com'
receivers = ['abc#abc.com','xyz#abc.com']
import unittest
import time
class TestDB1(unittest.TestCase):
def testing_master(self):
Master_Conn = 'Driver=
{SQLServer};Server=server\servername;Database=database;UID=userid;PWD=password'
Master_db = pyodbc.connect(Master_Conn)
Master_Cursor = Master_db.cursor()
try:
#Open, read and execute add_shell.sql
file = open('C:\\aaa\\add1.sql', 'r')
line = file.read()
lines = line.replace('\n', ' ')
file1 = open('C:\\aaa\\add2.sql', 'r')
line1=file1.read()
lines1=line1.replace('\n', ' ')
Master_Cursor.execute(lines)
time.sleep(1)
Master_Cursor.execute(lines1)
Master_db.commit()
file.close()
file1.close()
#Get python object for latest record inserted in DB1
Master_CID=Master_Cursor.execute("select col1 from tablename1 order by sequenceid desc").fetchone()
#convert tuple to srting [0] gives first tuple element.
Master_CID_str=str(Master_CID[0])
#Get GUID by stripping first 2 chars and last char.
Master_CID_str=Master_CID_str[2:len(Master_CID_str)-1]
Master_CLID=Master_Cursor.execute("select col2 from tablename2 order by sequenceid desc").fetchone()
Master_CLID_str=str(Master_CLID[0])
Master_CLID_str=Master_CLID_str[2:len(Master_CLID_str) - 1]
# Wait for service that transfers data from one db to another DB to run
time.sleep(310)
finally:
Master_Cursor.close()
Master_db.close()
return Master_CID,Master_CID_str,Master_CLID,Master_CLID_str
def testing_int_instance(self):
#unpacking return value of tuple from testing_master() function
Master_CID,Master_CID_str,Master_CLID,Master_CLID_str=self.testing_master()
print ("printing from testing_int_instance {0}".format(Master_CID))
Int_Instance_Conn = 'Driver={SQL Server};Server=server2\servername2;Database=database2;UID=uid;PWD=password;'
Int_db = pyodbc.connect(Int_Instance_Conn)
Int_Cursor = Int_db.cursor()
#return Int_db, Int_Cursor
#execute select from db where col matches that of one inserted in master db.
Int_Instance_CID=Int_Cursor.execute("select col1 from table1 where cartridgemodelid = '%s'" %(Master_CID_str)).fetchone()
print(Int_Instance_CID)
smtpObj = smtplib.SMTP('22.101.1.333', 25)
if (Master_CID==Int_Instance_CID):
print("Matched")
content="This email confirms successful data transfer from Master to Instance for col1: \n"
message = "\r\n".join(["From:" + sender,"To:" + str(receivers[:]),"Subject: Test Result","",content +Master_CID_str])
#smtpObj = smtplib.SMTP('22.101.2.222', 25)
smtpObj.sendmail(sender, receivers, message)
elif (Master_CID!=Int_Instance_CID):
print("no match")
content = "This email confirms failure of data transfer from DB1 to DB2 for COL1: \n"
message = "\r\n".join(["From:" + sender, "To:" + str(receivers[:]), "Subject: Test Result", "",content +Master_CID_str])
smtpObj.sendmail(sender, receivers, message)
Int_Instance_CLID=Int_Cursor.execute("select COL2 from table2 where col= '%s'" %(Master_CLID_str)).fetchone()
print (Int_Instance_CLID)
if (Master_CLID == Int_Instance_CLID):
print ("Printing int_instance CLID {0}".format(Int_Instance_CLID))
content = "This email confirms successful data transfer from DB1 to DB2 for COL: \n"
message = "\r\n".join(
["From:" + sender, "To:" + str(receivers[:]), "Subject: Test Result", "", content + Master_CLID_str])
#smtpObj = smtplib.SMTP('22.101.2.222', 25)
smtpObj.sendmail(sender, receivers, message)
print ("Ids Matched")
elif (Master_CLID != Int_Instance_CLID):
DB1 to DB2 for COL: \n"
message = "\r\n".join(
["From:" + sender, "To:" + str(receivers[:]), "Subject: Test Result", "", content + Master_CLID_str])
#smtpObj = smtplib.SMTP('22.101.2.222', 25)
smtpObj.sendmail(sender, receivers, message)
smtpObj.quit()
Int_db.close()
if name == 'main':
unittest.main()
add1.sql is:
DECLARE #Name VARCHAR(2000)
DECLARE #PartNumber VARCHAR(2000)
SELECT #Name='test'+convert(varchar,getdate(),108)
SELECT #PartNumber='17_00001_'+convert(varchar,getdate(),108)
DECLARE #XML XML
DECLARE #FileName VARCHAR(1000)
DECLARE #Id UNIQUEIDENTIFIER
SELECT #Id = NEWID()
SELECT #FileName = 'test.xml'
SELECT #XML='<model>
<xml tags go here>
BEGIN
INSERT INTO table1
(ID,Name,Type,Desc,Number,Revision,Model,status,Modifiedby,Modifiedon)
VALUES(#Id,#Name,'xyz','',#partnumber,'01',#XML,'A','453454-4545-4545-4543-345342343',GETUTCDATE())
add2.sql is:
DECLARE #XML XML
DECLARE #CM_Name VARCHAR(2000)
DECLARE #FileName VARCHAR(1000)
DECLARE #PartNumber VARCHAR(2000)
DECLARE #Id UNIQUEIDENTIFIER
SELECT #Id=NEWID()
DECLARE #Name VARCHAR(2000)
DECLARE #CMId VARCHAR(2000)
DECLARE #CM_PartName VARCHAR(2000)
DECLARE #CM_Partnumber VARCHAR(2000)
SELECT #Name='test'+convert(varchar,getdate(),108)
SELECT #PartNumber='test'+convert(varchar,getdate(),108)
DECLARE #RowCount INT
DECLARE #Message VARCHAR(100);
SELECT #FileName = 'test.xml'
SELECT #CMId = CM.CMID,
#CM_Name = CM.CMName,
#CM_PN = CM.PN
FROM cm.Model CM
WHERE CM.MName LIKE 'test%'
ORDER BY CM.ModifiedBy DESC
SELECT #XML='<Layout>
other xml tags...
BEGIN
INSERT INTO cm.CL(ID, ModelID, Layout, Description, PN, Revision, CLayout, Status, ModifiedBy, ModifiedOn)
SELECT TOP 1 #Id, #CMId, #Name, '', #PartNumber, '01', #XML, 'A', '453454-345-4534-4534-4534543545', GETUTCDATE()
FROM cm.table1 CM
WHERE CM.Name=#CM_Name
AND CM.Partnumber=#CM_Partnumber
Currently, you are calling test_master() twice! First as your named method and then in second method when you unpack the returned values. Below is a demonstration of defined methods outside of the Class object. If called as is, testing_master will run twice.
Consider also using a context manager to read .sql scripts using with() which handles open and close i/o operations shown below:
# FIRST CALL
def testing_master():
#...SAME CODE...
try:
with open('C:\\aaa\\add1.sql', 'r') as file:
lines = file.read().replace('\n', ' ')
Master_Cursor.execute(lines)
Master_db.commit()
time.sleep(1)
with open('C:\\aaa\\add2.sql', 'r') as file1:
lines1 = file1.read().replace('\n', ' ')
Master_Cursor.execute(lines1)
Master_db.commit()
#...SAME CODE...
return Master_CID, Master_CID_str, Master_CLID, Master_CLID_str
def testing_int_instance():
# SECOND CALL
Master_CID, Master_CID_str, Master_CLID, Master_CLID_str = testing_master()
#...SAME CODE...
if __name__ == "__main__":
testing_master()
testing_int_instance()
Commenting out the time(310) seems like it works but as you mention the background Windows service does not effectively run and so interrupts database transfer.
To resolve, consider calling the second method at the end of the first by passing the values as parameters without any return and remove unpacking line. Then, in the main global environment, only run testing_master(). Of course qualify with self when inside a Class definition.
def testing_master():
#...SAME CODE...
testing_int_instance(Master_CID, Master_CID_str, Master_CLID, Master_CLID_str)
def testing_int_instance(Master_CID, Master_CID_str, Master_CLID, Master_CLID_str):
#...SKIP UNPACK LINE
#...CONTINUE WITH SAME CODE...
if __name__ == "__main__":
testing_master()
Due to your unittest, consider slight adjustment to original setup where you qualify every variable with self:
def testing_master():
...
self.Master_CID=Master_Cursor.execute("select col1 from tablename1 order by sequenceid desc").fetchone()
self.Master_CID_str=str(Master_CID[0])
self.Master_CID_str=Master_CID_str[2:len(Master_CID_str)-1]
self.Master_CLID=Master_Cursor.execute("select col2 from tablename2 order by sequenceid desc").fetchone()
self.Master_CLID_str=str(Master_CLID[0])
self.Master_CLID_str=Master_CLID_str[2:len(Master_CLID_str) - 1]
def testing_int_instance(self):
# NO UNPACK LINE
# ADD self. TO EVERY Master_* VARIABLE
...