Skip to content

Commit be43ef2

Browse files
committed
Refactoring and adding some new examples!
1 parent 7124809 commit be43ef2

11 files changed

+525
-103
lines changed

PySimpleSQL.py

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ def set_callback(self,callback,fctn):
162162
:param fctn: The function to call. Note, the function must take in two parameters, a @Database instance, and a @PySimpleGUI.Window instance, and return True or False
163163
:return: None
164164
"""
165-
callback=callback.lower()
165+
166166
supported=[
167167
'before_save', 'after_save', 'before_delete', 'after_delete',
168168
'before_update', 'after_update', # Aliases for before/after_save
@@ -506,7 +506,7 @@ def add_selector(self, control): # _listBox,_pk,_field):
506506
# Associate a listbox with this query object. This will be used to select the appropriate record
507507
# self.selector={'control':_listBox,'pk':_pk,'field':_field}
508508

509-
if type(control) not in [sg.PySimpleGUI.Listbox, sg.PySimpleGUI.Slider, sg.Combo]:
509+
if type(control) not in [sg.PySimpleGUI.Listbox, sg.PySimpleGUI.Slider, sg.Combo, sg.Table]:
510510
raise RuntimeError(f'add_selector() error: {control} is not a supported control.')
511511

512512
logger.info(f'Adding {control.Key} as a selector for the {self.table} table.')
@@ -770,19 +770,20 @@ def set_callback(self, callback, fctn):
770770
:param fctn: The function to call. Note, the function must take in two parameters, a @Database instance, and a @PySimpleGUI.Window instance
771771
:return: None
772772
"""
773-
callback = callback.lower()
774-
supported = [
775-
'update_controls', 'edit_enable', 'edit_disable'
776-
]
777-
# Add in support fow Window keys
773+
supported = ['update_controls', 'edit_enable', 'edit_disable']
778774

775+
# Add in mapped controls
779776
for control in self.control_map:
780-
supported.append(control['control'].Key.lower())
777+
supported.append(control['control'].Key)
778+
779+
# Add in other window controls
780+
for control in self.window.AllKeysDict:
781+
supported.append(control)
781782

782783
if callback in supported:
783784
self.callbacks[callback] = fctn
784785
else:
785-
raise RuntimeError( f'Callback "{callback}" not supported.')
786+
raise RuntimeError( f'Callback "{callback}" not supported. callback: {callback} supported: {supported}')
786787

787788
def auto_bind(self, win):
788789
"""
@@ -1069,9 +1070,10 @@ def update_controls(self,table=''): # table type: str
10691070

10701071
updated_val = None
10711072

1072-
if d['control'].Key.lower() in self.callbacks:
1073-
# The user has set a callback for this control, we will use it!
1074-
updated_val=self.callbacks[d['control'].Key.lower()]()
1073+
# If there is a callback for this control, use it
1074+
if d['control'].Key in self.callbacks:
1075+
print(f'{ d["control"].Key} is in callbacks!')
1076+
self.callbacks[d['control'].Key]()
10751077

10761078
elif type(d['control']) is sg.PySimpleGUI.Combo:
10771079
# Update controls with foreign queries first
@@ -1103,32 +1105,33 @@ def update_controls(self,table=''): # table type: str
11031105
elif type(d['control']) is sg.PySimpleGUI.Table:
11041106
# Tables use an array of arrays for values. Note that the headings can't be changed.
11051107
# Generate a new list!
1106-
updated_val=[[]] # List that will contain other lists
1108+
# List that will contain other lists
11071109
# TODO: Fix this with some kind of sane default behavior.
11081110
# For now, just use the Database callbacks to handle Tables
1109-
1111+
pass
11101112

11111113
elif type(d['control']) is sg.PySimpleGUI.InputText or type(d['control']) is sg.PySimpleGUI.Multiline:
11121114
# Lets now update the control in the GUI
11131115
# For text objects, lets clear the field...
11141116
d['control'].update('') # HACK for sqlite query not making needed keys! This will blank it out at least
1117+
updated_val = d['table'][d['field']]
11151118

11161119
elif type(d['control']) is sg.PySimpleGUI.Checkbox:
11171120
# TODO: FIXME
11181121
# d['control'].update(0)
11191122
# print('Checkbox...')
1120-
pass
1123+
updated_val = d['table'][d['field']]
11211124
else:
11221125
sg.popup(f'Unknown control type {type(d["control"])}')
11231126

1124-
# If no field has been set, we will get it from the query object
1125-
if not updated_val:
1126-
# print('updatedVal not set...')
1127-
updated_val = d['table'][d['field']]
11281127

11291128
# Finally, we will update the actual GUI control!
1130-
d['control'].update(updated_val)
1129+
if updated_val is not None:
1130+
d['control'].update(updated_val)
11311131

1132+
# ---------
1133+
# SELECTORS
1134+
# ---------
11321135
# We can update the selector controls
11331136
# We do it down here because it's not a mapped control...
11341137
# Check for selector events
@@ -1149,6 +1152,12 @@ def update_controls(self,table=''): # table type: str
11491152
l=len(table.rows)
11501153
control.update(value= table._current_index +1,range=(1,l))
11511154

1155+
elif type(d['control']) is sg.PySimpleGUI.Table:
1156+
# Make all the headings
1157+
for k,v in enumerate(self.rows()):
1158+
print(f'k: {k}')
1159+
print(f'v:{v}')
1160+
11521161

11531162

11541163
# Enable/Disable controls based on the edit protection button and presence of a record
@@ -1182,7 +1191,7 @@ def update_controls(self,table=''): # table type: str
11821191

11831192

11841193

1185-
# Run callback
1194+
# Run callbacks
11861195
if 'update_controls' in self.callbacks.keys():
11871196
# Running user update function
11881197
logger.info('Running the update_controls callback...')
@@ -1209,7 +1218,7 @@ def process_events(self, event, values):
12091218
for e in self.event_map:
12101219
if e['event'] == event:
12111220
logger.info(f'Executing event {event} via event mapping.')
1212-
e['function']()
1221+
e['function'](event,values)
12131222
return True
12141223

12151224
# Check for selector events
@@ -1379,6 +1388,15 @@ def selector(table,control=sg.LBox,size=None):
13791388
layout = [control(enable_events=True,size=size or _default_control_size,orientation='h',disable_number_display=True,key=key)]
13801389
elif control==sg.Combo:
13811390
layout=[control(values=(), size=size or _default_control_size, readonly=True, enable_events=True, key=key,auto_size_text=False)]
1391+
elif control==sg.Table:
1392+
# We have to get header information directly from the sqlite3 database, as the Database class has not been
1393+
# instanced yet!
1394+
headings=[]
1395+
q=f'PRAGMA table_info(table);'
1396+
1397+
for k,v in db[table].rows:
1398+
headings.append(f'k:{k} v:{v}')
1399+
layout=[control(values=([[1,2,3]]), headings=headings, enable_events=True, key=key)]
13821400
else:
13831401
raise RuntimeError(f'Control type "{control}" not supported as a selector.')
13841402
return layout

examples/example.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ CREATE TABLE "Restaurant"(
77
"pkRestaurant" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
88
"name" TEXT DEFAULT "New Restaurant",
99
"location" TEXT,
10-
"fkType" INTEGER,
10+
"fkType" INTEGER DEFAULT 1,
1111
FOREIGN KEY(fkType) REFERENCES Type(pkType)
1212
);
1313

1414
CREATE TABLE "Item"(
1515
"pkItem" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
1616
"name" TEXT DEFAULT "New Item",
1717
"fkRestaurant" INTEGER,
18-
"fkMenu" INTEGER,
18+
"fkMenu" INTEGER DEFAULT 1,
1919
"price" TEXT,
2020
"description" TEXT,
2121
FOREIGN KEY(fkRestaurant) REFERENCES Restaurant(pkRestaurant) ON UPDATE CASCADE,

examples/example2.py

Lines changed: 0 additions & 40 deletions
This file was deleted.

examples/journal.sql

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
CREATE TABLE Journal(
2+
"id" INTEGER NOT NULL PRIMARY KEY,
3+
"entry_date" INTEGER DEFAULT (date('now')),
4+
"mood_id" INTEGER,
5+
"title" TEXT DEFAULT "New Entry",
6+
"entry" TEXT,
7+
FOREIGN KEY (mood_id) REFERENCES Mood(id) --This line is important to the automatic functionality of PySimpleSQL~
8+
);
9+
CREATE TABLE Mood(
10+
"id" INTEGER NOT NULL PRIMARY KEY,
11+
"name" TEXT
12+
);
13+
INSERT INTO Mood VALUES (1,"Happy");
14+
INSERT INTO Mood VALUES (2,"Sad");
15+
INSERT INTO Mood VALUES (3,"Angry");
16+
INSERT INTO Mood VALUES (4,"Content");
17+
INSERT INTO Journal (id,mood_id,title,entry)VALUES (1,1,"My first entry!","I am excited to write my thoughts every day")

examples/journal_external.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import PySimpleGUI as sg
2+
import PySimpleSQL as ss # <=== PySimpleSQL lines will be marked like this. There's only a few!
3+
import logging
4+
logger=logging.getLogger(__name__)
5+
logging.basicConfig(level=logging.INFO) # <=== You can set the logging level here (NOTSET,DEBUG,INFO,WARNING,ERROR,CRITICAL)
6+
7+
# -------------------------
8+
# CREATE PYSIMPLEGUI LAYOUT
9+
# -------------------------
10+
# Define the columns for the table selector
11+
headings=['id','Date: ','Mood: ','Title: ']
12+
visible=[0,1,1,1] # Hide the id column
13+
layout=[
14+
ss.selector('sel_journal','Journal',sg.Table,num_rows=10,headings=headings,visible_column_map=visible),
15+
ss.actions('act_journal','Journal'),
16+
ss.record('Journal.entry_date'),
17+
ss.record('Journal.mood_id', sg.Combo, label='My mood:', size=(30,10), auto_size_text=False),
18+
ss.record('Journal.title'),
19+
ss.record('Journal.entry', sg.MLine, size=(71,20))
20+
]
21+
win=sg.Window('Journal example', layout, finalize=True)
22+
db=ss.Database('journal.db', win, sql_script='journal.sql') #<=== Here is the magic!
23+
# Note: sql_script in only run if journal.db does not exist! This has the effect of creating a new blank
24+
# database as defined by the sql_script file if the database does not yet exist, otherwise it will use the database!
25+
26+
# Reverse the default sort order so new journal entries appear at the top
27+
db['Journal'].set_order_clause('ORDER BY entry_date DESC')
28+
# Set the column order for search operations. By default, only the column designated as the description column is searched
29+
db['Journal'].set_search_order(['entry_Date','title','entry'])
30+
31+
# ---------
32+
# MAIN LOOP
33+
# ---------
34+
while True:
35+
event, values = win.read()
36+
37+
if db.process_events(event, values): # <=== let PySimpleSQL process its own events! Simple!
38+
logger.info(f'PySimpleDB event handler handled the event {event}!')
39+
elif event == sg.WIN_CLOSED or event == 'Exit':
40+
db=None # <= ensures proper closing of the sqlite database and runs a database optimization
41+
break
42+
else:
43+
logger.info(f'This event ({event}) is not yet handled.')
44+
45+
"""
46+
I hope that you enjoyed this simple demo of a Journal database.
47+
Without comments, this could have been done in about30 lines of code! Seriously - a full database-backed
48+
usable program! The combination of PySimpleSQL and PySimpleGUI is very fun, fast and powerful!
49+
50+
Learnings from this example:
51+
- Using Table.set_search_order() to set the search order of the table for search operations.
52+
- creating a default/empty database with an external sql script with the sql_script keyword argument to ss.Database()
53+
- using ss.record() and ss.selector() functions for easy GUI element creation
54+
- using the label keyword argument to ss.record() to define a custom label
55+
- using Tables as ss.selector() element types
56+
- changing the sort order of database tables
57+
"""

examples/journal_internal.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import PySimpleGUI as sg
2+
import PySimpleSQL as ss # <=== PySimpleSQL lines will be marked like this. There's only a few!
3+
import logging
4+
logger=logging.getLogger(__name__)
5+
logging.basicConfig(level=logging.INFO) # <=== You can set the logging level here (NOTSET,DEBUG,INFO,WARNING,ERROR,CRITICAL)
6+
7+
# -------------------------------------
8+
# CREATE A SIMPLE DATABASE TO WORK WITH
9+
# -------------------------------------
10+
sql="""
11+
CREATE TABLE Journal(
12+
"id" INTEGER NOT NULL PRIMARY KEY,
13+
"entry_date" INTEGER DEFAULT (date('now')),
14+
"mood_id" INTEGER,
15+
"title" TEXT DEFAULT "New Entry",
16+
"entry" TEXT,
17+
FOREIGN KEY (mood_id) REFERENCES Mood(id) --This line is important to the automatic functionality of PySimpleSQL~
18+
);
19+
CREATE TABLE Mood(
20+
"id" INTEGER NOT NULL PRIMARY KEY,
21+
"name" TEXT
22+
);
23+
INSERT INTO Mood VALUES (1,"Happy");
24+
INSERT INTO Mood VALUES (2,"Sad");
25+
INSERT INTO Mood VALUES (3,"Angry");
26+
INSERT INTO Mood VALUES (4,"Content");
27+
INSERT INTO Journal (id,mood_id,title,entry)VALUES (1,1,"My first entry!","I am excited to write my thoughts every day")
28+
"""
29+
30+
# -------------------------
31+
# CREATE PYSIMPLEGUI LAYOUT
32+
# -------------------------
33+
# Define the columns for the table selector
34+
headings=['id','Date: ','Mood: ','Title: ']
35+
visible=[0,1,1,1] # Hide the id column
36+
layout=[
37+
ss.selector('sel_journal','Journal',sg.Table,num_rows=10,headings=headings,visible_column_map=visible),
38+
ss.actions('act_journal','Journal'),
39+
ss.record('Journal.entry_date'),
40+
ss.record('Journal.mood_id', sg.Combo, label='My mood:', size=(30,10), auto_size_text=False),
41+
ss.record('Journal.title'),
42+
ss.record('Journal.entry', sg.MLine, size=(71,20))
43+
]
44+
win=sg.Window('Journal example', layout, finalize=True)
45+
db=ss.Database('journal.db', win, sql_commands=sql) #<=== Here is the magic!
46+
# Note: sql_commands in only run if journal.db does not exist! This has the effect of creating a new blank
47+
# database as defined by the sql_commands if the database does not yet exist, otherwise it will use the database!
48+
49+
# Reverse the default sort order so new journal entries appear at the top
50+
db['Journal'].set_order_clause('ORDER BY entry_date DESC')
51+
# Set the column order for search operations. By default, only the column designated as the description column is searched
52+
db['Journal'].set_search_order(['entry_Date','title','entry'])
53+
54+
# ---------
55+
# MAIN LOOP
56+
# ---------
57+
while True:
58+
event, values = win.read()
59+
60+
if db.process_events(event, values): # <=== let PySimpleSQL process its own events! Simple!
61+
logger.info(f'PySimpleDB event handler handled the event {event}!')
62+
elif event == sg.WIN_CLOSED or event == 'Exit':
63+
db=None # <= ensures proper closing of the sqlite database and runs a database optimization
64+
break
65+
else:
66+
logger.info(f'This event ({event}) is not yet handled.')
67+
68+
"""
69+
I hope that you enjoyed this simple demo of a Journal database.
70+
Without comments and embedded SQL script, this could have been done in well under 50 lines of code! Seriously - a full database-backed
71+
usable program! The combination of PySimpleSQL and PySimpleGUI is very fun, fast and powerful!
72+
73+
Learnings from this example:
74+
- Using Table.set_search_order() to set the search order of the table for search operations.
75+
- embedding sql commands in code for table creation
76+
- creating a default/empty database with sql commands with the sql_commands keyword argument to ss.Database()
77+
- using ss.record() and ss.selector() functions for easy GUI element creation
78+
- using the label keyword argument to ss.record() to define a custom label
79+
- using Tables as ss.selector() element types
80+
- changing the sort order of database tables
81+
"""

0 commit comments

Comments
 (0)