pysimplesql


Namepysimplesql JSON
Version 2.3.0 PyPI version JSON
download
home_pagehttps://github.com/PySimpleSQL/pysimplesql
SummarySqlite3 binding for PySimpleGUI
upload_time2023-02-04 00:19:58
maintainer
docs_urlNone
authorJonathan Decker
requires_python
licenseGNU General Public License v3 or later (GPLv3+)
keywords sql sqlite sqlite3 database front-end access libre office gui pysimplegui
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # **pysimplesql** User's Manual

## DISCLAIMER:
While **pysimplesql** works with and was inspired by the excellent PySimpleGUI™ project, it has no affiliation.

## Rapidly build and deploy database applications in Python
**pysimplesql** binds PySimpleGUI™ to sqlite3 databases for rapid, effortless database application development. Makes a great
replacement for MS Access or Libre Office Base! Have the full power and language features of Python while having the 
power and control of managing your own codebase. **pysimplesql** not only allows for super simple automatic control (not one single
line of SQL needs written to use **pysimplesql**), but also allows for very low level control for situations that warrant it.

## History
**pysimplesql** was conceived after having used PySimpleGUI™ to prototype a GUI in Python.  After some time it became apparent that
my approach of prototyping in one language, just to implement it in another wasn't very efficient and didn't make much sense.
I had taken this approach many times in the past due to the lack of a good RAD (Rapid Application Development) tool when it comes
to making database front ends in Python.  Rather than spending my time porting my prototype over, one time I decided to try my hand
at creating such a tool - and this is what I ended up with.
Now make no mistake - I'm not a good project maintainer, and my goal was never to launch an open source project in the first place!
The more I used this combination of **pysimplesql** and PySimpleGUI™ for my own database projects, the more I realized how many others 
would benefit from it. With that being said, I will do my best to maintain and improve this tool over time.  Being new to open source
as well as hosting projects like this, I have a lot to learn moving forward.  Your patience is appreciated.

## Basic Concepts
**pysimplesql** borrows on common concepts in other database front-end applications such as LibreOffice or MS Access.
The basic concept revolves around Forms, which are invisible containers that connect to an underlying database, and
Queries, which use SQL to access the tables within the database. Forms in **pysimplesql** are very flexible in that multiple forms (and their underlying databases and tables) can be bound to the same PySimpleGUI™ Window. This allows 
for a tremendous amount of flexibility in your projects. Binding a **pysimplesql** Form to a PySimpleGUI™ Window is
very easy, and automatically binds Elements of the Window to records in your own database.  Be sure to check out the 
many examples to get a quick idea of just how quick and easy it is to develop database application with the combination
of **pysimplesql** and PySimpleGUI™!

Some people may like to think of Form objects as a Database, and Query objects as a Table.  For this reason, the Form class
has an alias of Database and the Query class has an alias of Table - so you can use the **Database**/**Table** classes instead of
**Form**/**Query** in your own code if you prefer!

# Lets do this!

## Install
NOTE: I will try to keep current progress updated on Pypi so that pip installs the latest version.
However, the single **pysimplesql.py** file can just as well be copied directly into the root folder of your own project.
```
pip install PySimpleGUI
pip install pysimplesql
or
pip3 install PySimpleGUI
pip3 install pysimplesql
```

**pysimplesql** is now in active development and constantly changing. When an update is available, a message similar to 
the following will be displayed in the output of the program:

```***** pysimplesql update to v0.0.5 available! Just run pip3 install pysimplesql --upgrade *****```

Be sure to update the package when you get this message, or from time to time with
the following command:
```
pip install pysimplesql --upgrade
```
or
```
pip3 install pysimplesql --upgrade
```

### This Code

```python
import PySimpleGUI as sg
import pysimplesql as ss                               # <=== PySimpleSQL lines will be marked like this.  There's only a few!
import logging
logger=logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG)               # <=== You can set the logging level here (NOTSET,DEBUG,INFO,WARNING,ERROR,CRITICAL)


# Define our layout. We will use the Form.record convenience function to create the controls
layout = [
    [ss.record('Restaurant.name')],
    [ss.record('Restaurant.location')],
    [ss.record('Restaurant.fkType', sg.Combo, size=(30,10), auto_size_text=False)]
]
sub_layout = [
    [ss.selector('selector1','Item',size=(35,10))],
    [
        sg.Col(
            layout=[
                [ss.record('Item.name')],
                [ss.record('Item.fkMenu', sg.Combo, size=(30,10), auto_size_text=False)],
                [ss.record('Item.price')],
                [ss.record('Item.description', sg.MLine, size=(30, 7))]
            ]
        )
    ],
    #[ss.actions('act_item','Item', edit_protect=False,navigation=False,save=False, search=False)]
]
layout.append([sg.Frame('Items', sub_layout)])
layout.append([ss.actions('act_restaurant','Restaurant')])

# Initialize our window and database, then bind them together
win = sg.Window('places to eat', layout, finalize=True)
# Create our Form
frm = ss.Form(':memory:', sql_script='example.sql', bind=win)      # <=== load the database
# NOTE: ":memory:" is a special database URL for in-memory databases


while True:
    event, values = win.read()

    if ss.process_events(event, values):                  # <=== let PySimpleSQL process its own events! Simple!
        logger.info('PySimpleDB event handler handled the event!')
    elif event == sg.WIN_CLOSED or event == 'Exit':
        frm=None              # <= ensures proper closing of the sqlite database and runs a database optimization at close
        break
    else:
        logger.info(f'This event ({event}) is not yet handled.')

```
along with this sqlite table
```sql
DROP TABLE IF EXISTS "Restaurant";
DROP TABLE IF EXISTS "Item";
DROP TABLE IF EXISTS "Type";
DROP TABLE IF EXISTS "Menu";

CREATE TABLE "Restaurant"(
	"pkRestaurant" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
	"name" TEXT DEFAULT "New Restaurant",
	"location" TEXT,
	"fkType" INTEGER,
	FOREIGN KEY(fkType) REFERENCES Type(pkType)
);

CREATE TABLE "Item"(
	"pkItem" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
	"name" TEXT DEFAULT "New Item",
	"fkRestaurant" INTEGER,
	"fkMenu" INTEGER,
	"price" TEXT,
	"description" TEXT,
	FOREIGN KEY(fkRestaurant) REFERENCES Restaurant(pkRestaurant) ON UPDATE CASCADE,
	FOREIGN KEY(fkMenu) REFERENCES Menu(pkMenu)
);

CREATE TABLE "Type"(
	"pkType" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
	"name" TEXT DEFAULT "New Type"
);

CREATE TABLE "Menu"(
	"pkMenu" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
	"name" TEXT DEFAULT "New Menu"
);

INSERT INTO "Menu" VALUES (1,"Breakfast");
INSERT INTO "Menu" VALUES (2,"Lunch");
INSERT INTO "Menu" VALUES (3,"Dinner");

INSERT INTO "Type" VALUES (1,"Fast Food");
INSERT INTO "Type" VALUES (2,"Fine Dining");
INSERT INTO "Type" VALUES (3,"Hole in the wall");
INSERT INTO "Type" VALUES (4,"Chinese Food");

INSERT INTO "Restaurant" VALUES (1,"McDonalds","Seatle WA",1);
INSERT INTO "Item" VALUES (1,"Hamburger",1,2,"$4.99","Not flame broiled");
INSERT INTO "Item" VALUES (2,"Cheeseburger",1,2,"$5.99","With or without pickles");
INSERT INTO "Item" VALUES (3,"Big Breakfast",1,1,"$6.99","Your choice of bacon or sausage");

INSERT INTO "Restaurant" VALUES (2,"Chopstix","Cleveland OH",4);
INSERT INTO "Item" VALUES (4,"General Tso",2,3,"$7.99","Our best seller!");
INSERT INTO "Item" VALUES (5,"Teriaki Chicken",2,3,"$5.99","Comes on a stick");
INSERT INTO "Item" VALUES (6,"Sticky Rice",2,2,"$6.99","Our only lunch option, sorry!");

INSERT INTO "Restaurant" VALUES (3,"Jimbos","Lexington KY",3);
INSERT INTO "Item" VALUES (7,"Breakfast Pizza",3,1,"$11.99","Pizza in the morning");
INSERT INTO "Item" VALUES (8,"Lunch Pizza",3,3,"$12.99","Pizza at noon");
INSERT INTO "Item" VALUES (9,"Dinner Pizza",3,3,"$16.99","Whatever we did not sell earlier in the day");

```
### Makes This fully operational database front-end

![image](https://user-images.githubusercontent.com/70232210/91227678-e8c73700-e6f4-11ea-83ee-4712e687bfb4.png)

Like PySimpleGUI™, **pysimplesql** supports subscript notation, so your code can access the data easily in the format of
Form['Table']['column'].
In the example above, you could get the current item selection with the following code:
```python
selected_restaurant=frm['Restaurant']['name']
selected_item=frm['Item']['name']
```
or via the PySimpleGUI™ control elements with the following:
```python
selected_restaurant=win['Restaurant.name']
selected_item=win['Item.name']
```
### It really is that simple.  All of the heavy lifting is done in the background!

To get the best possible experience with **pysimplesql**, the magic is in the schema of the database.
The automatic functionality of **pysimplesql** relies on just a couple of things:
- foreign key constraints on the database tables (lets **pysimplesql** know what the relationships are, though manual 
relationship mapping is also available)
- a CASCADE ON UPDATE constraint on any tables that should automatically refresh child tables when parent tables are 
changed
- PySimpleGUI™ control keys need to be named {table}.{column} for automatic mapping.  Of course, manual mapping is 
supported as well. @Form.record() is a convenience function/"custom element" to make adding records quick and easy!
- The field 'name', (or the 2nd column of the database in the absence of a 'name' column) is what will display in 
comboxes for foreign key relationships.  Of course, this can be changed manually if needed, but truly the simplictiy of 
**pysimplesql** is in having everything happen automatically!

Here is another example sqlite table that shows the above rules at work.  Don't let this scare you, there are plenty of
tools to create your database without resorting to raw SQL commands. These commands here are just shown for completeness
(Creating the sqlite database is only done once anyway) 
```sql
CREATE TABLE "Book"(
    "pkBook" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    "title" TEXT,
    "author" TEXT
);
CREATE TABLE "Chapter"(
    "pkChapter" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    "title" TEXT,
    "fkBook" INTEGER,
    "startPage" INTEGER,
    -- SECRET SAUCE BELOW! If you have foreign key constraints set on the database,
    -- then pysimplesql will pick them up!
    -- note: ON UPDATE CASCADE only needed if you want automatic GUI refreshing
    -- (i.e. not every constraint needs them, like fields that will populate comboboxes for example)
    FOREIGN KEY(fkBook) REFERENCES Book(pkBook) ON UPDATE CASCADE
);
```

### But wait, there's more!
The above is literally all you have to know for working with simple and even moderate databases.  However, there is a 
lot of power in learning what is going on under the hood.  Starting with the fully automatic example above, we will work
backwards and unravel things to explain what is available to you for more control at a lower level.

#### **pysimplesql** elements:
Referencing the example above, look at the following:
```python
[ss.record('Restaurant.name')],

# could have been written like this using PySImpleGUI elements:
[sg.Text('Name:',size=(15,1)),sg.Input('',key='Restaurant.name',size=(30,1), metadata={'type': TYPE_RECORD})]
```
As you can see, the @pysimplesql.record() convenience function simplifies making record controls that adhere to the
**pysimplesql** naming convention of Table.column. Also notice that **pysimplesql**  makes use of the PySimpleGUI 
metadata keyword argument - but don't worry, the element's metadata is still be available to you in your own program by
adding your own keys in the Python list contained within.
There is even more you can do with this. The @pysimplesql.record() method can take a PySimpleGUI™ control element as a 
parameter as well, overriding the default Input() element.
See this code which creates a combobox instead:
```python
[ss.record('Restaurant.fkType', sg.Combo)]
```
Furthering that, the functions @pysimplesql.set_text_size() and @pysimplesql.set_control_size() can be used before calls 
to @pysimplesql.record() to have custom sizing of the control elements.  Even with these defaults set, the size parameter 
of @pysimplesql.record() will override the default control size, for plenty of flexibility.

Place those two functions just above the layout definition shown in the example above and then run the code again
```python
# set the sizing for the Restaurant section
ss.set_label_size(10, 1)
ss.set_control_size(90, 1)
layout = [
    [ss.record('Restaurant.name')],
    [ss.record('Restaurant.location')],
    [ss.record('Restaurant.fkType', sg.Combo, auto_size_text=False)]
]
sub_layout = [
    [ss.selector('selector1','Item')],
    [
        sg.Col(
            layout=[
                [ss.record('Item.name')],
                [ss.record('Item.fkMenu', sg.Combo, auto_size_text=False)],
                [ss.record('Item.price')],
                [ss.record('Item.description', sg.MLine, size=(30, 7))]
            ]
        )
    ],
    #[ss.actions('act_item','Item', edit_protect=False,navigation=False,save=False, search=False)]
]
layout.append([sg.Frame('Items', sub_layout)])
layout.append([ss.actions('act_restaurant','Restaurant')])
```
![image](https://user-images.githubusercontent.com/70232210/91287363-a71ea680-e75d-11ea-8b2f-d240c1ec2acf.png)
You will see that now, the controls were resized using the new sizing rules.  Notice however that the 'Description'
field isn't as wide as the others.  That is because the control size was overridden for just that single control (see code above).

Let's see one more example.  This time we will fix the oddly sized 'Description' field, as well as make the 'Restaurant' 
and 'Items' sections with their own sizing

```python
# set the sizing for the Restaurant section
ss.set_label_size(10, 1)
ss.set_control_size(90, 1)
layout = [
    [ss.record('Restaurant.name')],
    [ss.record('Restaurant.location')],
    [ss.record('Restaurant.fkType', sg.Combo, size=(30,10), auto_size_text=False)]
]
sub_layout = [
    [ss.selector('selector1','Item',size=(35,10))],
    [
        sg.Col(
            layout=[
                [ss.record('Item.name')],
                [ss.record('Item.fkMenu', sg.Combo, size=(30,10), auto_size_text=False)],
                [ss.record('Item.price')],
                [ss.record('Item.description', sg.MLine, size=(30, 7))]
            ]
        )
    ],
    #[ss.actions('act_item','Item', edit_protect=False,navigation=False,save=False, search=False)]
]
layout.append([sg.Frame('Items', sub_layout)])
layout.append([ss.actions('act_restaurant','Restaurant')])
```
![image](https://user-images.githubusercontent.com/70232210/91288080-8e62c080-e75e-11ea-8438-86035d4d6609.png)




### Binding the window to the element
Referencing the same example above, the window and database were bound with this one single line:
```python
frm = ss.Form(':memory:', 'example.sql', bind=win) # Load in the database and bind it to win
```
The above is a one-shot approach and all most users will ever need!
The above could have been written as:
```python
frm=ss.Form(':memory:', 'example.sql') # Load in the database
frm.bind(win) # automatically bind the window to the database
```

frm.bind() likewise can be peeled back to it's own components and could have been written like this:
```python
frm.auto_add_queries()
frm.auto_add_relationships()
frm.auto_map_controls(win)
frm.auto_map_events(win)
frm.requery_all()
frm.update_elements()
```

And finally, that brings us to the lowest-level functions for binding the database.
This is how you can MANUALLY map tables, relationships, controls and events to the database.
The above auto_map_* methods could have been manually achieved as follows:
```python
# Add the queries you want pysimplesql to handle.  The function frm.auto_add_tables() will add all queries found in the database 
# by default.  However, you may only need to work with a couple of queries in the database, and this is how you would do that
frm.add_query('Restaurant','pkRestaurant','name') # add the table Restaurant, with it's primary key field, and descriptive field (for comboboxes)
frm.add_query('Item','pkItem','name') # Note: While I personally prefer to use the pk{Query} and fk{Query} naming
frm.add_query('Type','pkType','name') #       conventions, it's not necessary for pySimpleSQL
frm.add_query('Menu','pkMenu','name') #       These could have just as well been restaurantID and itemID for example

# Set up relationships
# Notice below that the first relationship has the last parameter to True.  This is what the ON UPDATE CASCADE constraint accomplishes.
# Basically what it means is that then the Restaurant table is requeried, the associated Item table will automatically requery right after.
# This is what allows the GUI to seamlessly update all of the control elements when records are changed!
# The other relationships have that parameter set to False - they still have a relationship, but they don't need requeried automatically
frm.add_relationship('LEFT JOIN', 'Item', 'fkRestaurant', 'Restaurant', 'pkRestaurant', True) 
frm.add_relationship('LEFT JOIN', 'Restaurant', 'fkType', 'Type', 'pkType', False)
frm.add_relationship('LEFT JOIN', 'Item', 'fkMenu', 'Menu', 'pkMenu', False)

# Map our controls
# Note that you can map any control to any Query/field combination that you would like.
# The {Query}.{field} naming convention is only necessary if you want to use the auto-mapping functionality of pysimplesql!
frm.map_control(win['Restaurant.name'],'Restaurant','name')
frm.map_control(win['Restaurant.location'],'Restaurant','location')
frm.map_control(win['Restaurant.fkType'],'Type','pkType')
frm.map_control(win['Item.name'],'Item','name')
frm.map_control(win['Item.fkRestaurant'],'Item','fkRestaurant')
frm.map_control(win['Item.fkMenu'],'Item','fkMenu')
frm.map_control(win['Item.price'],'Item','price')
frm.map_control(win['Item.description'],'Item','description')

# Map out our events
# In the above example, this was all done in the background, as we used convenience functions to add record navigation buttons.
# However, we could have made our own buttons and mapped them to events.  Below is such an example
frm.map_event('Edit.Restaurant.First',db['Restaurant'].First) # button control with the key of 'Edit.Restaurant.First'
                                                             # mapped to the Query.First method
frm.map_event('Edit.Restaurant.Previous',db['Restaurant'].Previous)
frm.map_event('Edit.Restaurant.Next',db['Restaurant'].Next)
frm.map_event('Edit.Restaurant.Last',db['Restaurant'].Last)
# and so on...
# In fact, you can use the event mapper however you want to, mapping control names to any function you would like!
# Event mapping will be covered in more detail later...

# This is the magic function which populates all of the controls we mapped!
# For your convience, you can optionally use the function Form.set_callback('update_controls',function) to set a callback function
# that will be called every time the controls are updated.  This allows you to do custom things like update
# a preview image, change control parameters or just about anythong you want!
frm.update_elements()
```

As you can see, there is a lot of power in the auto functionality of pysimplesql, and you should take advantage of it any time you can.  Only very specific cases need to reach this lower level of manual configuration and mapping!

# BREAKDOWN OF ADVANCED FUNCTIONALITY
**pysimplesql** does much more than just bridge the gap between PySimpleGUI™ and Sqlite databases! In full, **pysimplesql** contains:
* Convenience functions for simplifying PySimpleGUI™ layout code
* Control binding between PySimpleGUI™ controls and Sqlite database fields
* Automatic requerying of related tables
* Record navigation - Such as First, Previous, Next, Last, Searching and selector controls
* Callbacks allow your own functions to expand control over your own database front ends
* Event Mapping

We will break each of these down below to give you a better understanding of how each of these features works.
## Convenience Functions
There are currently only a few convenience functions to aid in quickly creating PySimpleGUI™ layout code
pysimplesql.set_text_size(width,height) - Sets the PySimpleGUI™ text size for subsequent calls to Form.record(). Defaults to (15,1) otherwise.

pysimplesql.set_control_size(width, height) - Sets the PySImpleGUI™ control size for subsequent calls to Form.record(). Defaults to (30,1) otherwise.

pysimplesql.record(table, field,control_type=None,size=None,text_label=None)- This is a convenience function for creating a PySimpleGUI™ text element and a PySimpleGUI™ Input element inline for purposes of displaying a record from the database.  This function also creates the naming convention (table.column) in the control's key parameter that **pysimplesql** uses for advanced automatic functionality. The optional control_type parameter allows you to bind control types other than Input to a database field.  Checkboxes, listboxes and other controls entered here will override the default Input control. The size parameter will override the default control size that was set with Database.set_control_size().  Lastly, the text_label parameter will prefix a text field before the control.

pysimplesql.selector() -  for adding Selector controls to your GUI.  Selectors are responsible for selecting the current record in a Form

pysimplesql.actions()- Actions such as save, delete, search and navigation can all be customized with this convenience function!

## Control Binding
    TODO
## Automatic Requerying
    TODO
## Record Navigation
**pysimplesql** includes a convenience function for adding record navigation buttons to your project.  For lower level control or a custom look, you may want to learn how to do this on your own.  Lets start with the convenience function and work backwards from there to see how you can implement your own record navigation controls.

The convenience function pysimplesql.actions() is a swiss army knife when it comes to generating PySimpleGUI™ layout code for your record navigation controls.  With it, you can add First, Previous, Next and Last record navigation buttons, a search box, edit protection modes, and record actions such as Insert, Save and Delete (Or any combination of these items).  Under the hood, the actions() convenience function uses the Event Mapping features of **pysimplesql**, and your own code can do this too!
See the code below on example usage of the **pysimplesql**.actions() convenience function

```python
#!/usr/bin/python3
import PySimpleGUI as sg
import pysimplesql as ss

# Create a small table just for demo purposes
sql = '''
CREATE TABLE "Fruit"(
    "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
	"name" TEXT DEFAULT "New Fruit"
);
INSERT INTO "Fruit" ("name") VALUES ("Apple");
INSERT INTO "Fruit" ("name") VALUES ("Orange");
INSERT INTO "Fruit" ("name") VALUES ("Banana");
INSERT INTO "Fruit" ("name") VALUES ("Kiwi");
'''

# PySimpleGUI™ layout code to create your own navigation buttons
table = 'Fruit'  # This is the table in the database that you want to navigate
layout = [
    [ss.record(table, 'name', label='Fruit Name')],  # pysimplesql.record() convenience function for easy record creation!
    [ss.actions(table)]  # pysimplesql.actions() convenience function for easy navigation controls!
]

win = sg.Window('Navigation demo', layout, finalize=True)
# note: Since win was passed as a parameter, binding is automatic (including event mapping!)
# Also note, in-memory databases can be created with ":memory:"!
db = ss.Database(':memory:', sql_commands=sql, bind=win) #<- Database can be used as an alias to Form!

while True:
    event, values = win.read()
    if db.process_events(event, values):  # <=== let pysimplesql process its own events! Simple!
        print(f'PySimpleDB event handler handled the event {event}!')
    elif event == sg.WIN_CLOSED or event == 'Exit':
        db = None  # <= ensures proper closing of the sqlite database and runs a database optimization
        break
    else:
        print(f'This event ({event}) is not yet handled.')
```
Simple!
But as stated earlier, **pysimplesql**.actions is a swiss army knife!  Experiment with the code ablove, trying all of these variations to see all of goodness this convenience functions provides!

```python
ss.actions(table, search=False)
ss.actions(table, save=False)
ss.actions(table, edit_protect=False)
ss.actions(table, insert=False)
ss.actions(table, delete=False, save=False) 
```


See example below of how your can make your own record navigation controls instead of using the **pysimplesql**.actions() convenience function:

```python
#!/usr/bin/python3
import PySimpleGUI as sg
import pysimplesql as ss

# Create a small table just for demo purposes
sql = '''
CREATE TABLE "Fruit"(
    "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
	"name" TEXT DEFAULT "New Fruit"
);
INSERT INTO "Fruit" ("name") VALUES ("Apple");
INSERT INTO "Fruit" ("name") VALUES ("Orange");
INSERT INTO "Fruit" ("name") VALUES ("Banana");
INSERT INTO "Fruit" ("name") VALUES ("Kiwi");
'''

# PySimpleGUI™ layout code to create your own navigation buttons
table = 'Fruit'  # This is the table in the database that you want to navigate
layout = [
    [ss.record(table, 'name', label='Fruit Name')],  # pysimplesql.record() convenience function for easy record creation!
    # Below we will create navigation buttons manually, naming the key so that the automatic event mapper will map the events
    [sg.Button('<<', key=f'btnFirst', size=(1, 1), metadata=meta = {'type': ss.TYPE_EVENT, 'event_type': ss.EVENT_FIRST, 'table': table, 'function': None}),
     sg.Button('<', key=f'btnPrevious', size=(1, 1), metadata=meta = {'type': ss.TYPE_EVENT, 'event_type': ss.EVENT_PREVIOUS, 'table': table, 'function': None}),
     sg.Button('>', key=f'btnNext', size=(1, 1), metadata=meta = {'type': ss.TYPE_EVENT, 'event_type': ss.EVENT_NEXT, 'table': table, 'function': None}),
     sg.Button('>>', key=f'btnLast', size=(1, 1), metadata=meta = {'type': ss.TYPE_EVENT, 'event_type': ss.EVENT_LAST, 'table': table, 'function': None})
     ]
]

win = sg.Window('Navigation demo', layout, finalize=True)
# note: Since win was passed as a parameter, binding is automatic (including event mapping!)
# Also note, in-memory databases can be created with ":memory:"!
db = ss.Database(':memory:', win, sql_commands=sql)

while True:
    event, values = win.read()
    if db.process_events(event, values):  # <=== let pysimplesql process its own events! Simple!
        print(f'PySimpleDB event handler handled the event {event}!')
    elif event == sg.WIN_CLOSED or event == 'Exit':
        db = None  # <= ensures proper closing of the sqlite database and runs a database optimization
        break
    else:
        print(f'This event ({event}) is not yet handled.')
```
Notice the metadata use in the navigation buttons above.  This is so that the Automatic event mapping of **pysimplesql** will handle these.  Valid event_types can be found right at the start of the pysimplesql.py file.

Peeling this back further, you can rewrite the same without the special metadata used by the automatic event mapper, then manually map them in the event mapper yourself...

```python
#!/usr/bin/python3
import PySimpleGUI as sg
import pysimplesql as ss

# Create a small table just for demo purposes
sql = '''
CREATE TABLE "Fruit"(
    "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
	"name" TEXT DEFAULT "New Fruit"
);
INSERT INTO "Fruit" ("name") VALUES ("Apple");
INSERT INTO "Fruit" ("name") VALUES ("Orange");
INSERT INTO "Fruit" ("name") VALUES ("Banana");
INSERT INTO "Fruit" ("name") VALUES ("Kiwi");
'''

# PySimpleGUI™ layout code to create your own navigation buttons
table = 'Fruit'  # This is the table in the database that you want to navigate
layout = [
    ss.record(table, 'name', label='Fruit Name'),  # pysimplesql.record() convenience function for easy record creation!
    # Below we will create navigation buttons manually, naming the key so that the automatic event mapper will map the events
    [
        sg.Button('<<', key=f'btnFirst', size=(1, 1)),
        sg.Button('<', key=f'btnPrevious', size=(1, 1)),
        sg.Button('>', key=f'btnNext', size=(1, 1)),
        sg.Button('>>', key=f'btnLast', size=(1, 1))
    ]
]

win = sg.Window('Navigation demo', layout, finalize=True)
# note: Since win was passed as a parameter, binding is automatic (including event mapping!)
# Also note, in-memory databases can be created with ":memory:"!
db = ss.Database(':memory:', sql_commands=sql,bind=win)

# Manually map the events, since we did not adhere to the naming convention that the automatic mapper expects
db.map_event('btnFirst', db[table].first)
db.map_event('btnPrevious', db[table].previous)
db.map_event('btnNext', db[table].next)
db.map_event('btnLast', db[table].last)

while True:
    event, values = win.read()
    if db.process_events(event, values):  # <=== let pysimplesql process its own events! Simple!
        print(f'PySimpleDB event handler handled the event {event}!')
    elif event == sg.WIN_CLOSED or event == 'Exit':
        db = None  # <= ensures proper closing of the sqlite database and runs a database optimization
        break
    else:
        print(f'This event ({event}) is not yet handled.')
```

Lastly, you can rewrite the same and handle the events yourself instead of relying on **pysimplesql**'s event mapper

```python
#!/usr/bin/python3
import PySimpleGUI as sg
import pysimplesql as ss

# Create a small table just for demo purposes
sql = '''
CREATE TABLE "Fruit"(
    "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
	"name" TEXT DEFAULT "New Fruit"
);
INSERT INTO "Fruit" ("name") VALUES ("Apple");
INSERT INTO "Fruit" ("name") VALUES ("Orange");
INSERT INTO "Fruit" ("name") VALUES ("Banana");
INSERT INTO "Fruit" ("name") VALUES ("Kiwi");
'''

# PySimpleGUI™ layout code to create your own navigation buttons
table = 'Fruit'  # This is the table in the database that you want to navigate
layout = [
    ss.record(table, 'name', label='Fruit Name'),  # pysimplesql.record() convenience function for easy record creation!
    # Below we will create navigation buttons manually, naming the key so that the automatic event mapper will map the events
    [
        sg.Button('<<', key=f'btnFirst', size=(1, 1)),
        sg.Button('<', key=f'btnPrevious', size=(1, 1)),
        sg.Button('>', key=f'btnNext', size=(1, 1)),
        sg.Button('>>', key=f'btnLast', size=(1, 1))
    ]
]

win = sg.Window('Navigation demo', layout, finalize=True)
# note: Since win was passed as a parameter, binding is automatic (including event mapping!)
# Also note, in-memory databases can be created with ":memory:"!
db = ss.Database(':memory:', win, sql_commands=sql)

while True:
    event, values = win.read()
    # Manually handle our record selector events, bypassing the event mapper completely
    if db.process_events(event, values):  # <=== let pysimplesql process its own events! Simple!
        print(f'PySimpleDB event handler handled the event {event}!')
    elif event == 'btnFirst':
        db[table].first()
    elif event == 'btnPrevious':
        db[table].previous()
    elif event == 'btnNext':
        db[table].next()
    elif event == 'btnLast':
        db[table].last()
    elif event == sg.WIN_CLOSED or event == 'Exit':
        db = None  # <= ensures proper closing of the sqlite database and runs a database optimization
        break
    else:
        print(f'This event ({event}) is not yet handled.')

```

Whether you want to use the **pysimplesql**.actions() convenience function, write your own navigation button layout code, use the auto event mapper, manually map the events, or handle the events yourself, you have plenty of options for flexibility writing your navigation button code!  Of course, the convenience function is very flexible and has attractive icons in the buttons, and really should be used in most cases.
## Callbacks
 TODO
## Event Mapping
 TODO

## SIMPLE BUT ROBUST PROMPT SAVE SYSTEM
Nothing is worse than a program that doesn't catch when you forget to save changes - especially if those programs deal with data entry. **pysimplesql** has a simple but robust prompt save system in place.  This is enabled by default, but can be turned off if needed. Prompt saves can be thought of as having 3 levels - a Form level which affects all queries of the Form, a Query level which affects only specific queries, and a manual level where you can command the system to prompt to save changes (such as when switching tabs in a tab group, at specified intervals, or when shutting down your program). The system is smart enough to only prompt if an actual change is found.
### Form-level prompt save
Simply call ```python frm.set_promt_save(True) # or False to disable``` to enable automatic promt saves any time the user navigates away from a record that has changed.  This happens for any and all Queries attached to this Form.
### Query-level prompt save
A call to ```python frm['table_name'].set_prompt_save(True) # or False to disable for this Query``` can enable/disable automatic prompting for individual Queries
### Manual prompting
To manually prompt for a save, just do a direct call to ```python frm.prompt_save().  There is an optional autosave=True/False parameter to enable an autosave feature which will make these saves happen automatically without bothering the user for their input.  Its also a great thing to put in your main loop exit conditions to ensure changes are saved before shutting down.  There are a couple of caveats to using the prompt_save() call on the main loop exit condition - please see example below:
```python
# For using the prompt save system on exit, you have to add the enable_close_attempted_event=True parameter during PySimpleGUI window creation
window=sg.Window('My Program', layout, enable_close_attempted_event=True)

While True:
	events,values=window.read()

	if event in (sg.WINDOW_CLOSE_ATTEMPTED_EVENT, sg.WIN_CLOSED, 'Exit', '-ESCAPE-'):
        	frm.prompt_save(autosave=False) # set autosave to True to have this automatically happen, or leave to False to have the user prompted
		window.close()
		frm=None
		break
```

## PLEASE BE PATIENT
There is a lot of documentation left to do, and more examples to make.  In subsequent releases, I'll try to pick away at 
these items to get them done.  For now, just create a github issue and ask your questions and I'll do my best to guide
you in the right direction!



            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/PySimpleSQL/pysimplesql",
    "name": "pysimplesql",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "SQL,sqlite,sqlite3,database,front-end,access,libre office,GUI,PySimpleGUI",
    "author": "Jonathan Decker",
    "author_email": "pysimplesql@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/1c/07/7a456d8ec98aa6cd1610a7f5b638e6a9627f04a5efbac9600a7829d63b4d/pysimplesql-2.3.0.tar.gz",
    "platform": "ALL",
    "description": "# **pysimplesql** User's Manual\n\n## DISCLAIMER:\nWhile **pysimplesql** works with and was inspired by the excellent PySimpleGUI\u2122 project, it has no affiliation.\n\n## Rapidly build and deploy database applications in Python\n**pysimplesql** binds PySimpleGUI\u2122 to sqlite3 databases for rapid, effortless database application development. Makes a great\nreplacement for MS Access or Libre Office Base! Have the full power and language features of Python while having the \npower and control of managing your own codebase. **pysimplesql** not only allows for super simple automatic control (not one single\nline of SQL needs written to use **pysimplesql**), but also allows for very low level control for situations that warrant it.\n\n## History\n**pysimplesql** was conceived after having used PySimpleGUI\u2122 to prototype a GUI in Python.  After some time it became apparent that\nmy approach of prototyping in one language, just to implement it in another wasn't very efficient and didn't make much sense.\nI had taken this approach many times in the past due to the lack of a good RAD (Rapid Application Development) tool when it comes\nto making database front ends in Python.  Rather than spending my time porting my prototype over, one time I decided to try my hand\nat creating such a tool - and this is what I ended up with.\nNow make no mistake - I'm not a good project maintainer, and my goal was never to launch an open source project in the first place!\nThe more I used this combination of **pysimplesql** and PySimpleGUI\u2122 for my own database projects, the more I realized how many others \nwould benefit from it. With that being said, I will do my best to maintain and improve this tool over time.  Being new to open source\nas well as hosting projects like this, I have a lot to learn moving forward.  Your patience is appreciated.\n\n## Basic Concepts\n**pysimplesql** borrows on common concepts in other database front-end applications such as LibreOffice or MS Access.\nThe basic concept revolves around Forms, which are invisible containers that connect to an underlying database, and\nQueries, which use SQL to access the tables within the database. Forms in **pysimplesql** are very flexible in that multiple forms (and their underlying databases and tables) can be bound to the same PySimpleGUI\u2122 Window. This allows \nfor a tremendous amount of flexibility in your projects. Binding a **pysimplesql** Form to a PySimpleGUI\u2122 Window is\nvery easy, and automatically binds Elements of the Window to records in your own database.  Be sure to check out the \nmany examples to get a quick idea of just how quick and easy it is to develop database application with the combination\nof **pysimplesql** and PySimpleGUI\u2122!\n\nSome people may like to think of Form objects as a Database, and Query objects as a Table.  For this reason, the Form class\nhas an alias of Database and the Query class has an alias of Table - so you can use the **Database**/**Table** classes instead of\n**Form**/**Query** in your own code if you prefer!\n\n# Lets do this!\n\n## Install\nNOTE: I will try to keep current progress updated on Pypi so that pip installs the latest version.\nHowever, the single **pysimplesql.py** file can just as well be copied directly into the root folder of your own project.\n```\npip install PySimpleGUI\npip install pysimplesql\nor\npip3 install PySimpleGUI\npip3 install pysimplesql\n```\n\n**pysimplesql** is now in active development and constantly changing. When an update is available, a message similar to \nthe following will be displayed in the output of the program:\n\n```***** pysimplesql update to v0.0.5 available! Just run pip3 install pysimplesql --upgrade *****```\n\nBe sure to update the package when you get this message, or from time to time with\nthe following command:\n```\npip install pysimplesql --upgrade\n```\nor\n```\npip3 install pysimplesql --upgrade\n```\n\n### This Code\n\n```python\nimport PySimpleGUI as sg\nimport pysimplesql as ss                               # <=== PySimpleSQL lines will be marked like this.  There's only a few!\nimport logging\nlogger=logging.getLogger(__name__)\nlogging.basicConfig(level=logging.DEBUG)               # <=== You can set the logging level here (NOTSET,DEBUG,INFO,WARNING,ERROR,CRITICAL)\n\n\n# Define our layout. We will use the Form.record convenience function to create the controls\nlayout = [\n    [ss.record('Restaurant.name')],\n    [ss.record('Restaurant.location')],\n    [ss.record('Restaurant.fkType', sg.Combo, size=(30,10), auto_size_text=False)]\n]\nsub_layout = [\n    [ss.selector('selector1','Item',size=(35,10))],\n    [\n        sg.Col(\n            layout=[\n                [ss.record('Item.name')],\n                [ss.record('Item.fkMenu', sg.Combo, size=(30,10), auto_size_text=False)],\n                [ss.record('Item.price')],\n                [ss.record('Item.description', sg.MLine, size=(30, 7))]\n            ]\n        )\n    ],\n    #[ss.actions('act_item','Item', edit_protect=False,navigation=False,save=False, search=False)]\n]\nlayout.append([sg.Frame('Items', sub_layout)])\nlayout.append([ss.actions('act_restaurant','Restaurant')])\n\n# Initialize our window and database, then bind them together\nwin = sg.Window('places to eat', layout, finalize=True)\n# Create our Form\nfrm = ss.Form(':memory:', sql_script='example.sql', bind=win)      # <=== load the database\n# NOTE: \":memory:\" is a special database URL for in-memory databases\n\n\nwhile True:\n    event, values = win.read()\n\n    if ss.process_events(event, values):                  # <=== let PySimpleSQL process its own events! Simple!\n        logger.info('PySimpleDB event handler handled the event!')\n    elif event == sg.WIN_CLOSED or event == 'Exit':\n        frm=None              # <= ensures proper closing of the sqlite database and runs a database optimization at close\n        break\n    else:\n        logger.info(f'This event ({event}) is not yet handled.')\n\n```\nalong with this sqlite table\n```sql\nDROP TABLE IF EXISTS \"Restaurant\";\nDROP TABLE IF EXISTS \"Item\";\nDROP TABLE IF EXISTS \"Type\";\nDROP TABLE IF EXISTS \"Menu\";\n\nCREATE TABLE \"Restaurant\"(\n\t\"pkRestaurant\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n\t\"name\" TEXT DEFAULT \"New Restaurant\",\n\t\"location\" TEXT,\n\t\"fkType\" INTEGER,\n\tFOREIGN KEY(fkType) REFERENCES Type(pkType)\n);\n\nCREATE TABLE \"Item\"(\n\t\"pkItem\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n\t\"name\" TEXT DEFAULT \"New Item\",\n\t\"fkRestaurant\" INTEGER,\n\t\"fkMenu\" INTEGER,\n\t\"price\" TEXT,\n\t\"description\" TEXT,\n\tFOREIGN KEY(fkRestaurant) REFERENCES Restaurant(pkRestaurant) ON UPDATE CASCADE,\n\tFOREIGN KEY(fkMenu) REFERENCES Menu(pkMenu)\n);\n\nCREATE TABLE \"Type\"(\n\t\"pkType\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n\t\"name\" TEXT DEFAULT \"New Type\"\n);\n\nCREATE TABLE \"Menu\"(\n\t\"pkMenu\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n\t\"name\" TEXT DEFAULT \"New Menu\"\n);\n\nINSERT INTO \"Menu\" VALUES (1,\"Breakfast\");\nINSERT INTO \"Menu\" VALUES (2,\"Lunch\");\nINSERT INTO \"Menu\" VALUES (3,\"Dinner\");\n\nINSERT INTO \"Type\" VALUES (1,\"Fast Food\");\nINSERT INTO \"Type\" VALUES (2,\"Fine Dining\");\nINSERT INTO \"Type\" VALUES (3,\"Hole in the wall\");\nINSERT INTO \"Type\" VALUES (4,\"Chinese Food\");\n\nINSERT INTO \"Restaurant\" VALUES (1,\"McDonalds\",\"Seatle WA\",1);\nINSERT INTO \"Item\" VALUES (1,\"Hamburger\",1,2,\"$4.99\",\"Not flame broiled\");\nINSERT INTO \"Item\" VALUES (2,\"Cheeseburger\",1,2,\"$5.99\",\"With or without pickles\");\nINSERT INTO \"Item\" VALUES (3,\"Big Breakfast\",1,1,\"$6.99\",\"Your choice of bacon or sausage\");\n\nINSERT INTO \"Restaurant\" VALUES (2,\"Chopstix\",\"Cleveland OH\",4);\nINSERT INTO \"Item\" VALUES (4,\"General Tso\",2,3,\"$7.99\",\"Our best seller!\");\nINSERT INTO \"Item\" VALUES (5,\"Teriaki Chicken\",2,3,\"$5.99\",\"Comes on a stick\");\nINSERT INTO \"Item\" VALUES (6,\"Sticky Rice\",2,2,\"$6.99\",\"Our only lunch option, sorry!\");\n\nINSERT INTO \"Restaurant\" VALUES (3,\"Jimbos\",\"Lexington KY\",3);\nINSERT INTO \"Item\" VALUES (7,\"Breakfast Pizza\",3,1,\"$11.99\",\"Pizza in the morning\");\nINSERT INTO \"Item\" VALUES (8,\"Lunch Pizza\",3,3,\"$12.99\",\"Pizza at noon\");\nINSERT INTO \"Item\" VALUES (9,\"Dinner Pizza\",3,3,\"$16.99\",\"Whatever we did not sell earlier in the day\");\n\n```\n### Makes This fully operational database front-end\n\n![image](https://user-images.githubusercontent.com/70232210/91227678-e8c73700-e6f4-11ea-83ee-4712e687bfb4.png)\n\nLike PySimpleGUI\u2122, **pysimplesql** supports subscript notation, so your code can access the data easily in the format of\nForm['Table']['column'].\nIn the example above, you could get the current item selection with the following code:\n```python\nselected_restaurant=frm['Restaurant']['name']\nselected_item=frm['Item']['name']\n```\nor via the PySimpleGUI\u2122 control elements with the following:\n```python\nselected_restaurant=win['Restaurant.name']\nselected_item=win['Item.name']\n```\n### It really is that simple.  All of the heavy lifting is done in the background!\n\nTo get the best possible experience with **pysimplesql**, the magic is in the schema of the database.\nThe automatic functionality of **pysimplesql** relies on just a couple of things:\n- foreign key constraints on the database tables (lets **pysimplesql** know what the relationships are, though manual \nrelationship mapping is also available)\n- a CASCADE ON UPDATE constraint on any tables that should automatically refresh child tables when parent tables are \nchanged\n- PySimpleGUI\u2122 control keys need to be named {table}.{column} for automatic mapping.  Of course, manual mapping is \nsupported as well. @Form.record() is a convenience function/\"custom element\" to make adding records quick and easy!\n- The field 'name', (or the 2nd column of the database in the absence of a 'name' column) is what will display in \ncomboxes for foreign key relationships.  Of course, this can be changed manually if needed, but truly the simplictiy of \n**pysimplesql** is in having everything happen automatically!\n\nHere is another example sqlite table that shows the above rules at work.  Don't let this scare you, there are plenty of\ntools to create your database without resorting to raw SQL commands. These commands here are just shown for completeness\n(Creating the sqlite database is only done once anyway) \n```sql\nCREATE TABLE \"Book\"(\n    \"pkBook\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n    \"title\" TEXT,\n    \"author\" TEXT\n);\nCREATE TABLE \"Chapter\"(\n    \"pkChapter\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n    \"title\" TEXT,\n    \"fkBook\" INTEGER,\n    \"startPage\" INTEGER,\n    -- SECRET SAUCE BELOW! If you have foreign key constraints set on the database,\n    -- then pysimplesql will pick them up!\n    -- note: ON UPDATE CASCADE only needed if you want automatic GUI refreshing\n    -- (i.e. not every constraint needs them, like fields that will populate comboboxes for example)\n    FOREIGN KEY(fkBook) REFERENCES Book(pkBook) ON UPDATE CASCADE\n);\n```\n\n### But wait, there's more!\nThe above is literally all you have to know for working with simple and even moderate databases.  However, there is a \nlot of power in learning what is going on under the hood.  Starting with the fully automatic example above, we will work\nbackwards and unravel things to explain what is available to you for more control at a lower level.\n\n#### **pysimplesql** elements:\nReferencing the example above, look at the following:\n```python\n[ss.record('Restaurant.name')],\n\n# could have been written like this using PySImpleGUI elements:\n[sg.Text('Name:',size=(15,1)),sg.Input('',key='Restaurant.name',size=(30,1), metadata={'type': TYPE_RECORD})]\n```\nAs you can see, the @pysimplesql.record() convenience function simplifies making record controls that adhere to the\n**pysimplesql** naming convention of Table.column. Also notice that **pysimplesql**  makes use of the PySimpleGUI \nmetadata keyword argument - but don't worry, the element's metadata is still be available to you in your own program by\nadding your own keys in the Python list contained within.\nThere is even more you can do with this. The @pysimplesql.record() method can take a PySimpleGUI\u2122 control element as a \nparameter as well, overriding the default Input() element.\nSee this code which creates a combobox instead:\n```python\n[ss.record('Restaurant.fkType', sg.Combo)]\n```\nFurthering that, the functions @pysimplesql.set_text_size() and @pysimplesql.set_control_size() can be used before calls \nto @pysimplesql.record() to have custom sizing of the control elements.  Even with these defaults set, the size parameter \nof @pysimplesql.record() will override the default control size, for plenty of flexibility.\n\nPlace those two functions just above the layout definition shown in the example above and then run the code again\n```python\n# set the sizing for the Restaurant section\nss.set_label_size(10, 1)\nss.set_control_size(90, 1)\nlayout = [\n    [ss.record('Restaurant.name')],\n    [ss.record('Restaurant.location')],\n    [ss.record('Restaurant.fkType', sg.Combo, auto_size_text=False)]\n]\nsub_layout = [\n    [ss.selector('selector1','Item')],\n    [\n        sg.Col(\n            layout=[\n                [ss.record('Item.name')],\n                [ss.record('Item.fkMenu', sg.Combo, auto_size_text=False)],\n                [ss.record('Item.price')],\n                [ss.record('Item.description', sg.MLine, size=(30, 7))]\n            ]\n        )\n    ],\n    #[ss.actions('act_item','Item', edit_protect=False,navigation=False,save=False, search=False)]\n]\nlayout.append([sg.Frame('Items', sub_layout)])\nlayout.append([ss.actions('act_restaurant','Restaurant')])\n```\n![image](https://user-images.githubusercontent.com/70232210/91287363-a71ea680-e75d-11ea-8b2f-d240c1ec2acf.png)\nYou will see that now, the controls were resized using the new sizing rules.  Notice however that the 'Description'\nfield isn't as wide as the others.  That is because the control size was overridden for just that single control (see code above).\n\nLet's see one more example.  This time we will fix the oddly sized 'Description' field, as well as make the 'Restaurant' \nand 'Items' sections with their own sizing\n\n```python\n# set the sizing for the Restaurant section\nss.set_label_size(10, 1)\nss.set_control_size(90, 1)\nlayout = [\n    [ss.record('Restaurant.name')],\n    [ss.record('Restaurant.location')],\n    [ss.record('Restaurant.fkType', sg.Combo, size=(30,10), auto_size_text=False)]\n]\nsub_layout = [\n    [ss.selector('selector1','Item',size=(35,10))],\n    [\n        sg.Col(\n            layout=[\n                [ss.record('Item.name')],\n                [ss.record('Item.fkMenu', sg.Combo, size=(30,10), auto_size_text=False)],\n                [ss.record('Item.price')],\n                [ss.record('Item.description', sg.MLine, size=(30, 7))]\n            ]\n        )\n    ],\n    #[ss.actions('act_item','Item', edit_protect=False,navigation=False,save=False, search=False)]\n]\nlayout.append([sg.Frame('Items', sub_layout)])\nlayout.append([ss.actions('act_restaurant','Restaurant')])\n```\n![image](https://user-images.githubusercontent.com/70232210/91288080-8e62c080-e75e-11ea-8438-86035d4d6609.png)\n\n\n\n\n### Binding the window to the element\nReferencing the same example above, the window and database were bound with this one single line:\n```python\nfrm = ss.Form(':memory:', 'example.sql', bind=win) # Load in the database and bind it to win\n```\nThe above is a one-shot approach and all most users will ever need!\nThe above could have been written as:\n```python\nfrm=ss.Form(':memory:', 'example.sql') # Load in the database\nfrm.bind(win) # automatically bind the window to the database\n```\n\nfrm.bind() likewise can be peeled back to it's own components and could have been written like this:\n```python\nfrm.auto_add_queries()\nfrm.auto_add_relationships()\nfrm.auto_map_controls(win)\nfrm.auto_map_events(win)\nfrm.requery_all()\nfrm.update_elements()\n```\n\nAnd finally, that brings us to the lowest-level functions for binding the database.\nThis is how you can MANUALLY map tables, relationships, controls and events to the database.\nThe above auto_map_* methods could have been manually achieved as follows:\n```python\n# Add the queries you want pysimplesql to handle.  The function frm.auto_add_tables() will add all queries found in the database \n# by default.  However, you may only need to work with a couple of queries in the database, and this is how you would do that\nfrm.add_query('Restaurant','pkRestaurant','name') # add the table Restaurant, with it's primary key field, and descriptive field (for comboboxes)\nfrm.add_query('Item','pkItem','name') # Note: While I personally prefer to use the pk{Query} and fk{Query} naming\nfrm.add_query('Type','pkType','name') #       conventions, it's not necessary for pySimpleSQL\nfrm.add_query('Menu','pkMenu','name') #       These could have just as well been restaurantID and itemID for example\n\n# Set up relationships\n# Notice below that the first relationship has the last parameter to True.  This is what the ON UPDATE CASCADE constraint accomplishes.\n# Basically what it means is that then the Restaurant table is requeried, the associated Item table will automatically requery right after.\n# This is what allows the GUI to seamlessly update all of the control elements when records are changed!\n# The other relationships have that parameter set to False - they still have a relationship, but they don't need requeried automatically\nfrm.add_relationship('LEFT JOIN', 'Item', 'fkRestaurant', 'Restaurant', 'pkRestaurant', True) \nfrm.add_relationship('LEFT JOIN', 'Restaurant', 'fkType', 'Type', 'pkType', False)\nfrm.add_relationship('LEFT JOIN', 'Item', 'fkMenu', 'Menu', 'pkMenu', False)\n\n# Map our controls\n# Note that you can map any control to any Query/field combination that you would like.\n# The {Query}.{field} naming convention is only necessary if you want to use the auto-mapping functionality of pysimplesql!\nfrm.map_control(win['Restaurant.name'],'Restaurant','name')\nfrm.map_control(win['Restaurant.location'],'Restaurant','location')\nfrm.map_control(win['Restaurant.fkType'],'Type','pkType')\nfrm.map_control(win['Item.name'],'Item','name')\nfrm.map_control(win['Item.fkRestaurant'],'Item','fkRestaurant')\nfrm.map_control(win['Item.fkMenu'],'Item','fkMenu')\nfrm.map_control(win['Item.price'],'Item','price')\nfrm.map_control(win['Item.description'],'Item','description')\n\n# Map out our events\n# In the above example, this was all done in the background, as we used convenience functions to add record navigation buttons.\n# However, we could have made our own buttons and mapped them to events.  Below is such an example\nfrm.map_event('Edit.Restaurant.First',db['Restaurant'].First) # button control with the key of 'Edit.Restaurant.First'\n                                                             # mapped to the Query.First method\nfrm.map_event('Edit.Restaurant.Previous',db['Restaurant'].Previous)\nfrm.map_event('Edit.Restaurant.Next',db['Restaurant'].Next)\nfrm.map_event('Edit.Restaurant.Last',db['Restaurant'].Last)\n# and so on...\n# In fact, you can use the event mapper however you want to, mapping control names to any function you would like!\n# Event mapping will be covered in more detail later...\n\n# This is the magic function which populates all of the controls we mapped!\n# For your convience, you can optionally use the function Form.set_callback('update_controls',function) to set a callback function\n# that will be called every time the controls are updated.  This allows you to do custom things like update\n# a preview image, change control parameters or just about anythong you want!\nfrm.update_elements()\n```\n\nAs you can see, there is a lot of power in the auto functionality of pysimplesql, and you should take advantage of it any time you can.  Only very specific cases need to reach this lower level of manual configuration and mapping!\n\n# BREAKDOWN OF ADVANCED FUNCTIONALITY\n**pysimplesql** does much more than just bridge the gap between PySimpleGUI\u2122 and Sqlite databases! In full, **pysimplesql** contains:\n* Convenience functions for simplifying PySimpleGUI\u2122 layout code\n* Control binding between PySimpleGUI\u2122 controls and Sqlite database fields\n* Automatic requerying of related tables\n* Record navigation - Such as First, Previous, Next, Last, Searching and selector controls\n* Callbacks allow your own functions to expand control over your own database front ends\n* Event Mapping\n\nWe will break each of these down below to give you a better understanding of how each of these features works.\n## Convenience Functions\nThere are currently only a few convenience functions to aid in quickly creating PySimpleGUI\u2122 layout code\npysimplesql.set_text_size(width,height) - Sets the PySimpleGUI\u2122 text size for subsequent calls to Form.record(). Defaults to (15,1) otherwise.\n\npysimplesql.set_control_size(width, height) - Sets the PySImpleGUI\u2122 control size for subsequent calls to Form.record(). Defaults to (30,1) otherwise.\n\npysimplesql.record(table, field,control_type=None,size=None,text_label=None)- This is a convenience function for creating a PySimpleGUI\u2122 text element and a PySimpleGUI\u2122 Input element inline for purposes of displaying a record from the database.  This function also creates the naming convention (table.column) in the control's key parameter that **pysimplesql** uses for advanced automatic functionality. The optional control_type parameter allows you to bind control types other than Input to a database field.  Checkboxes, listboxes and other controls entered here will override the default Input control. The size parameter will override the default control size that was set with Database.set_control_size().  Lastly, the text_label parameter will prefix a text field before the control.\n\npysimplesql.selector() -  for adding Selector controls to your GUI.  Selectors are responsible for selecting the current record in a Form\n\npysimplesql.actions()- Actions such as save, delete, search and navigation can all be customized with this convenience function!\n\n## Control Binding\n    TODO\n## Automatic Requerying\n    TODO\n## Record Navigation\n**pysimplesql** includes a convenience function for adding record navigation buttons to your project.  For lower level control or a custom look, you may want to learn how to do this on your own.  Lets start with the convenience function and work backwards from there to see how you can implement your own record navigation controls.\n\nThe convenience function pysimplesql.actions() is a swiss army knife when it comes to generating PySimpleGUI\u2122 layout code for your record navigation controls.  With it, you can add First, Previous, Next and Last record navigation buttons, a search box, edit protection modes, and record actions such as Insert, Save and Delete (Or any combination of these items).  Under the hood, the actions() convenience function uses the Event Mapping features of **pysimplesql**, and your own code can do this too!\nSee the code below on example usage of the **pysimplesql**.actions() convenience function\n\n```python\n#!/usr/bin/python3\nimport PySimpleGUI as sg\nimport pysimplesql as ss\n\n# Create a small table just for demo purposes\nsql = '''\nCREATE TABLE \"Fruit\"(\n    \"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n\t\"name\" TEXT DEFAULT \"New Fruit\"\n);\nINSERT INTO \"Fruit\" (\"name\") VALUES (\"Apple\");\nINSERT INTO \"Fruit\" (\"name\") VALUES (\"Orange\");\nINSERT INTO \"Fruit\" (\"name\") VALUES (\"Banana\");\nINSERT INTO \"Fruit\" (\"name\") VALUES (\"Kiwi\");\n'''\n\n# PySimpleGUI\u2122 layout code to create your own navigation buttons\ntable = 'Fruit'  # This is the table in the database that you want to navigate\nlayout = [\n    [ss.record(table, 'name', label='Fruit Name')],  # pysimplesql.record() convenience function for easy record creation!\n    [ss.actions(table)]  # pysimplesql.actions() convenience function for easy navigation controls!\n]\n\nwin = sg.Window('Navigation demo', layout, finalize=True)\n# note: Since win was passed as a parameter, binding is automatic (including event mapping!)\n# Also note, in-memory databases can be created with \":memory:\"!\ndb = ss.Database(':memory:', sql_commands=sql, bind=win) #<- Database can be used as an alias to Form!\n\nwhile True:\n    event, values = win.read()\n    if db.process_events(event, values):  # <=== let pysimplesql process its own events! Simple!\n        print(f'PySimpleDB event handler handled the event {event}!')\n    elif event == sg.WIN_CLOSED or event == 'Exit':\n        db = None  # <= ensures proper closing of the sqlite database and runs a database optimization\n        break\n    else:\n        print(f'This event ({event}) is not yet handled.')\n```\nSimple!\nBut as stated earlier, **pysimplesql**.actions is a swiss army knife!  Experiment with the code ablove, trying all of these variations to see all of goodness this convenience functions provides!\n\n```python\nss.actions(table, search=False)\nss.actions(table, save=False)\nss.actions(table, edit_protect=False)\nss.actions(table, insert=False)\nss.actions(table, delete=False, save=False) \n```\n\n\nSee example below of how your can make your own record navigation controls instead of using the **pysimplesql**.actions() convenience function:\n\n```python\n#!/usr/bin/python3\nimport PySimpleGUI as sg\nimport pysimplesql as ss\n\n# Create a small table just for demo purposes\nsql = '''\nCREATE TABLE \"Fruit\"(\n    \"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n\t\"name\" TEXT DEFAULT \"New Fruit\"\n);\nINSERT INTO \"Fruit\" (\"name\") VALUES (\"Apple\");\nINSERT INTO \"Fruit\" (\"name\") VALUES (\"Orange\");\nINSERT INTO \"Fruit\" (\"name\") VALUES (\"Banana\");\nINSERT INTO \"Fruit\" (\"name\") VALUES (\"Kiwi\");\n'''\n\n# PySimpleGUI\u2122 layout code to create your own navigation buttons\ntable = 'Fruit'  # This is the table in the database that you want to navigate\nlayout = [\n    [ss.record(table, 'name', label='Fruit Name')],  # pysimplesql.record() convenience function for easy record creation!\n    # Below we will create navigation buttons manually, naming the key so that the automatic event mapper will map the events\n    [sg.Button('<<', key=f'btnFirst', size=(1, 1), metadata=meta = {'type': ss.TYPE_EVENT, 'event_type': ss.EVENT_FIRST, 'table': table, 'function': None}),\n     sg.Button('<', key=f'btnPrevious', size=(1, 1), metadata=meta = {'type': ss.TYPE_EVENT, 'event_type': ss.EVENT_PREVIOUS, 'table': table, 'function': None}),\n     sg.Button('>', key=f'btnNext', size=(1, 1), metadata=meta = {'type': ss.TYPE_EVENT, 'event_type': ss.EVENT_NEXT, 'table': table, 'function': None}),\n     sg.Button('>>', key=f'btnLast', size=(1, 1), metadata=meta = {'type': ss.TYPE_EVENT, 'event_type': ss.EVENT_LAST, 'table': table, 'function': None})\n     ]\n]\n\nwin = sg.Window('Navigation demo', layout, finalize=True)\n# note: Since win was passed as a parameter, binding is automatic (including event mapping!)\n# Also note, in-memory databases can be created with \":memory:\"!\ndb = ss.Database(':memory:', win, sql_commands=sql)\n\nwhile True:\n    event, values = win.read()\n    if db.process_events(event, values):  # <=== let pysimplesql process its own events! Simple!\n        print(f'PySimpleDB event handler handled the event {event}!')\n    elif event == sg.WIN_CLOSED or event == 'Exit':\n        db = None  # <= ensures proper closing of the sqlite database and runs a database optimization\n        break\n    else:\n        print(f'This event ({event}) is not yet handled.')\n```\nNotice the metadata use in the navigation buttons above.  This is so that the Automatic event mapping of **pysimplesql** will handle these.  Valid event_types can be found right at the start of the pysimplesql.py file.\n\nPeeling this back further, you can rewrite the same without the special metadata used by the automatic event mapper, then manually map them in the event mapper yourself...\n\n```python\n#!/usr/bin/python3\nimport PySimpleGUI as sg\nimport pysimplesql as ss\n\n# Create a small table just for demo purposes\nsql = '''\nCREATE TABLE \"Fruit\"(\n    \"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n\t\"name\" TEXT DEFAULT \"New Fruit\"\n);\nINSERT INTO \"Fruit\" (\"name\") VALUES (\"Apple\");\nINSERT INTO \"Fruit\" (\"name\") VALUES (\"Orange\");\nINSERT INTO \"Fruit\" (\"name\") VALUES (\"Banana\");\nINSERT INTO \"Fruit\" (\"name\") VALUES (\"Kiwi\");\n'''\n\n# PySimpleGUI\u2122 layout code to create your own navigation buttons\ntable = 'Fruit'  # This is the table in the database that you want to navigate\nlayout = [\n    ss.record(table, 'name', label='Fruit Name'),  # pysimplesql.record() convenience function for easy record creation!\n    # Below we will create navigation buttons manually, naming the key so that the automatic event mapper will map the events\n    [\n        sg.Button('<<', key=f'btnFirst', size=(1, 1)),\n        sg.Button('<', key=f'btnPrevious', size=(1, 1)),\n        sg.Button('>', key=f'btnNext', size=(1, 1)),\n        sg.Button('>>', key=f'btnLast', size=(1, 1))\n    ]\n]\n\nwin = sg.Window('Navigation demo', layout, finalize=True)\n# note: Since win was passed as a parameter, binding is automatic (including event mapping!)\n# Also note, in-memory databases can be created with \":memory:\"!\ndb = ss.Database(':memory:', sql_commands=sql,bind=win)\n\n# Manually map the events, since we did not adhere to the naming convention that the automatic mapper expects\ndb.map_event('btnFirst', db[table].first)\ndb.map_event('btnPrevious', db[table].previous)\ndb.map_event('btnNext', db[table].next)\ndb.map_event('btnLast', db[table].last)\n\nwhile True:\n    event, values = win.read()\n    if db.process_events(event, values):  # <=== let pysimplesql process its own events! Simple!\n        print(f'PySimpleDB event handler handled the event {event}!')\n    elif event == sg.WIN_CLOSED or event == 'Exit':\n        db = None  # <= ensures proper closing of the sqlite database and runs a database optimization\n        break\n    else:\n        print(f'This event ({event}) is not yet handled.')\n```\n\nLastly, you can rewrite the same and handle the events yourself instead of relying on **pysimplesql**'s event mapper\n\n```python\n#!/usr/bin/python3\nimport PySimpleGUI as sg\nimport pysimplesql as ss\n\n# Create a small table just for demo purposes\nsql = '''\nCREATE TABLE \"Fruit\"(\n    \"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n\t\"name\" TEXT DEFAULT \"New Fruit\"\n);\nINSERT INTO \"Fruit\" (\"name\") VALUES (\"Apple\");\nINSERT INTO \"Fruit\" (\"name\") VALUES (\"Orange\");\nINSERT INTO \"Fruit\" (\"name\") VALUES (\"Banana\");\nINSERT INTO \"Fruit\" (\"name\") VALUES (\"Kiwi\");\n'''\n\n# PySimpleGUI\u2122 layout code to create your own navigation buttons\ntable = 'Fruit'  # This is the table in the database that you want to navigate\nlayout = [\n    ss.record(table, 'name', label='Fruit Name'),  # pysimplesql.record() convenience function for easy record creation!\n    # Below we will create navigation buttons manually, naming the key so that the automatic event mapper will map the events\n    [\n        sg.Button('<<', key=f'btnFirst', size=(1, 1)),\n        sg.Button('<', key=f'btnPrevious', size=(1, 1)),\n        sg.Button('>', key=f'btnNext', size=(1, 1)),\n        sg.Button('>>', key=f'btnLast', size=(1, 1))\n    ]\n]\n\nwin = sg.Window('Navigation demo', layout, finalize=True)\n# note: Since win was passed as a parameter, binding is automatic (including event mapping!)\n# Also note, in-memory databases can be created with \":memory:\"!\ndb = ss.Database(':memory:', win, sql_commands=sql)\n\nwhile True:\n    event, values = win.read()\n    # Manually handle our record selector events, bypassing the event mapper completely\n    if db.process_events(event, values):  # <=== let pysimplesql process its own events! Simple!\n        print(f'PySimpleDB event handler handled the event {event}!')\n    elif event == 'btnFirst':\n        db[table].first()\n    elif event == 'btnPrevious':\n        db[table].previous()\n    elif event == 'btnNext':\n        db[table].next()\n    elif event == 'btnLast':\n        db[table].last()\n    elif event == sg.WIN_CLOSED or event == 'Exit':\n        db = None  # <= ensures proper closing of the sqlite database and runs a database optimization\n        break\n    else:\n        print(f'This event ({event}) is not yet handled.')\n\n```\n\nWhether you want to use the **pysimplesql**.actions() convenience function, write your own navigation button layout code, use the auto event mapper, manually map the events, or handle the events yourself, you have plenty of options for flexibility writing your navigation button code!  Of course, the convenience function is very flexible and has attractive icons in the buttons, and really should be used in most cases.\n## Callbacks\n TODO\n## Event Mapping\n TODO\n\n## SIMPLE BUT ROBUST PROMPT SAVE SYSTEM\nNothing is worse than a program that doesn't catch when you forget to save changes - especially if those programs deal with data entry. **pysimplesql** has a simple but robust prompt save system in place.  This is enabled by default, but can be turned off if needed. Prompt saves can be thought of as having 3 levels - a Form level which affects all queries of the Form, a Query level which affects only specific queries, and a manual level where you can command the system to prompt to save changes (such as when switching tabs in a tab group, at specified intervals, or when shutting down your program). The system is smart enough to only prompt if an actual change is found.\n### Form-level prompt save\nSimply call ```python frm.set_promt_save(True) # or False to disable``` to enable automatic promt saves any time the user navigates away from a record that has changed.  This happens for any and all Queries attached to this Form.\n### Query-level prompt save\nA call to ```python frm['table_name'].set_prompt_save(True) # or False to disable for this Query``` can enable/disable automatic prompting for individual Queries\n### Manual prompting\nTo manually prompt for a save, just do a direct call to ```python frm.prompt_save().  There is an optional autosave=True/False parameter to enable an autosave feature which will make these saves happen automatically without bothering the user for their input.  Its also a great thing to put in your main loop exit conditions to ensure changes are saved before shutting down.  There are a couple of caveats to using the prompt_save() call on the main loop exit condition - please see example below:\n```python\n# For using the prompt save system on exit, you have to add the enable_close_attempted_event=True parameter during PySimpleGUI window creation\nwindow=sg.Window('My Program', layout, enable_close_attempted_event=True)\n\nWhile True:\n\tevents,values=window.read()\n\n\tif event in (sg.WINDOW_CLOSE_ATTEMPTED_EVENT, sg.WIN_CLOSED, 'Exit', '-ESCAPE-'):\n        \tfrm.prompt_save(autosave=False) # set autosave to True to have this automatically happen, or leave to False to have the user prompted\n\t\twindow.close()\n\t\tfrm=None\n\t\tbreak\n```\n\n## PLEASE BE PATIENT\nThere is a lot of documentation left to do, and more examples to make.  In subsequent releases, I'll try to pick away at \nthese items to get them done.  For now, just create a github issue and ask your questions and I'll do my best to guide\nyou in the right direction!\n\n\n",
    "bugtrack_url": null,
    "license": "GNU General Public License v3 or later (GPLv3+)",
    "summary": "Sqlite3 binding for PySimpleGUI",
    "version": "2.3.0",
    "split_keywords": [
        "sql",
        "sqlite",
        "sqlite3",
        "database",
        "front-end",
        "access",
        "libre office",
        "gui",
        "pysimplegui"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "98eeb1e115b5c75041c51d6c3ed0356915c49c26be347d0403959559b068acc0",
                "md5": "073a0ba92ed9734c62856f1755bcffff",
                "sha256": "476d50c49923efea001282f2f08d4d5a88b63ba0802ac0772674fa8e49aa596b"
            },
            "downloads": -1,
            "filename": "pysimplesql-2.3.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "073a0ba92ed9734c62856f1755bcffff",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 106681,
            "upload_time": "2023-02-04T00:19:56",
            "upload_time_iso_8601": "2023-02-04T00:19:56.384388Z",
            "url": "https://files.pythonhosted.org/packages/98/ee/b1e115b5c75041c51d6c3ed0356915c49c26be347d0403959559b068acc0/pysimplesql-2.3.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "1c077a456d8ec98aa6cd1610a7f5b638e6a9627f04a5efbac9600a7829d63b4d",
                "md5": "67605f5afbb4cddf38b1ae088fb209c6",
                "sha256": "50a9bb3e2ef7e6c210436f20365d30434dc5e2a0e0ab36742dfd60388fc88bd3"
            },
            "downloads": -1,
            "filename": "pysimplesql-2.3.0.tar.gz",
            "has_sig": false,
            "md5_digest": "67605f5afbb4cddf38b1ae088fb209c6",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 125465,
            "upload_time": "2023-02-04T00:19:58",
            "upload_time_iso_8601": "2023-02-04T00:19:58.836971Z",
            "url": "https://files.pythonhosted.org/packages/1c/07/7a456d8ec98aa6cd1610a7f5b638e6a9627f04a5efbac9600a7829d63b4d/pysimplesql-2.3.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-02-04 00:19:58",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "github_user": "PySimpleSQL",
    "github_project": "pysimplesql",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "pysimplesql"
}
        
Elapsed time: 0.04415s