17. Using the PostgreSQL DBMS
The PostgreSQL DBMS is freely available. It is an alternative to the "community" version of MySQL.
We are using it here to demonstrate that it is fairly simple to migrate Python/MySQL scripts to Python/PostgreSQL scripts.
With the MySQL DBMS, the architecture of our scripts was as follows:

With the PostgreSQL DBMS, it will be as follows:

17.1. Installing the PostgreSQL DBMS
PostgreSQL DBMS distributions are available at the URL [https://www.postgresql.org/download/] (May 2019). We demonstrate the installation of the 64-bit Windows version:


- In [1-4], download the DBMS installer;
Run the downloaded installer:

- In [6], specify an installation directory;

- In [8], the [Stack Builder] option is not needed for what we are doing here;
- in [10], leave the default value;

- In [12-13], we entered the password [root] here. This will be the password for the DBMS administrator, who is named [postgres]. PostgreSQL also refers to this as the superuser;
- In [15], leave the default value: this is the DBMS listening port;

- in [17], leave the default value;
- In [19], the summary of the installation configuration;


On Windows, the PostgreSQL DBMS is installed as a Windows service that starts automatically. Most of the time, this is not desirable. We will modify this configuration. Type [services] in the Windows search bar [24-26]:

- in [29], you can see that the PostgreSQL DBMS service is set to automatic mode. Change this by accessing the service properties [30]:

- In [31-32], set the startup type to Manual;
- in [33], stop the service;
When you want to start the DBMS manually, return to the [services] application, right-click on the [postgresql] service (34), and start it (35).
17.2. Administering PostgreSQL with the [pgAdmin] tool
Start the Windows service for the PostgreSQL DBMS (see previous paragraph). Then, in the same way you launched the [Services] tool, launch the [pgAdmin] tool, which allows you to administer the PostgreSQL DBMS [1-3]:

You may be prompted for the superuser password at some point. The superuser is named [postgres]. You set this password during the DBMS installation. In this document, we assigned the password [root] to the superuser during installation.
- In [4], [pgAdmin] is a web application;
- in [5], the list of PostgreSQL servers detected by [pgAdmin], here 1;
- in [6], the PostgreSQL server we started;
- in [7], the DBMS databases, here 1;
- in [8], the [postgresql] database is managed by the superuser [postgres];
First, let’s create a user [admpersonnes] with the password [nobody]:


- in [17], we entered [nobody];

- in [21], the SQL code that the [pgAdmin] tool will send to the PostgreSQL DBMS. This is a way to learn PostgreSQL’s proprietary SQL language;
- In [22], after confirming with the [Save] wizard, the user [admpersonnes] has been created;
Now we create the [dbpersonnes] database:

Right-click on [23], then [24-25] to create a new database. In the [26] tab, define the database name [27] and its owner [admpersonnes] [28].

- In [30], the SQL code for creating the database;
- In [31], after confirming with the [Save] wizard, the database [dbpersonnes] is created;
We will use the [dbpersonnes] database with Python scripts.
17.3. Installing the Python Connector for the PostgreSQL DBMS

The diagram above shows a connector linking Python scripts to the PostgreSQL DBMS. There are several available. We will install the [psycopg2] connector. This is done in a Python terminal (regardless of the directory in which the terminal is open). The connector is installed using the command [pip install psycopg2]:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\troiscouches\v01\tests>pip install psycopg2
Collecting psycopg2
Downloading psycopg2-2.8.5-cp38-cp38-win_amd64.whl (1.1 MB)
|| 1.1 MB 3.2 MB/s
Installing collected packages: psycopg2
Successfully installed psycopg2-2.8.5
17.4. Porting MySQL scripts to PostgreSQL scripts

- The folder [1] containing the MySQL scripts is duplicated (Ctrl-C / Ctrl-V), then the file names are changed to match their contents;
17.4.1. [pgres_module]
This module is a copy of the [mysql_module] module (see section |script [mysql-04]: executing an SQL command file|). Change the imports:
Instead of:
# imports
from mysql.connector import DatabaseError, InterfaceError
from mysql.connector.connection import MySQLConnection
from mysql.connector.cursor import MySQLCursor
we write:
# imports
from psycopg2 import DatabaseError, InterfaceError
from psycopg2.extensions import connection, cursor
The signature of the [display_info] function was:
def display_info(cursor: MySQLCursor):
It becomes:
def display_info(cursor: cursor)
The signature of the [execute_list_of_commands] function was:
def execute_list_of_commands(connection: MySQLConnection, sql_commands: list,
track: bool = False, stop: bool = True, with_transaction: bool = True)
It becomes:
def execute_list_of_commands(connection: connection, sql_commands: list,
tracking: bool = False, stop: bool = True, with_transaction: bool = True):
Otherwise, nothing else changes.
17.4.2. script [pgres_01]
The [pgres_01] script is a copy of the [mysql_01] script (see section |script [mysql-01]: connecting to a MySQL database - 1|). The following changes are made:
Instead of:
# Import the mysql.connector module
from mysql.connector import connect, DatabaseError, InterfaceError
We write:
# Import the psycopg2 module
from psycopg2 import connect, DatabaseError, InterfaceError
The rest remains unchanged. The results are the same as with MySQL.
17.4.3. script [pgres_02]
The [pgres_02] script is a copy of the [mysql_02] script (see section |script [mysql-02]: connecting to a MySQL database - 2|). Make the following changes:
Instead of:
# import the mysql.connector module
from mysql.connector import DatabaseError, InterfaceError, connect
we write:
# import the psycopg2 module
from psycopg2 import DatabaseError, InterfaceError, connect
The results are not the same as those of the [mysql_02] script:
The [pgres_02] script is as follows:
# Import the mysql.connector module
from psycopg2 import DatabaseError, InterfaceError, connect
# ---------------------------------------------------------------------------------
def connection(host: str, database: str, login: str, pwd: str):
# connects and then disconnects (login, pwd) from the [database] on the [host] server
# raises a DatabaseError exception if there is a problem
connection = None
try:
# connection
connection = connect(host=host, user=login, password=pwd, database=database)
print(
f"Connection successful to database={database}, host={host} as user={login}, passwd={pwd}")
finally:
# Close the connection if it was opened
if connection:
connection.close()
print("Logout successful\n")
# ---------------------------------------------- main
# login credentials
USER = "admpersonnes"
PASSWD = "nobody"
HOST = "localhost"
DATABASE = "dbpeople"
# Log in an existing user
try:
connect(host=HOST, login=USER, pwd=PASSWD, database=DATABASE)
except (InterfaceError, DatabaseError) as error:
# display the error
print(error)
# connection for a non-existent user
try:
connect(host=HOST, login="xx", pwd="yy", database=DATABASE)
except (InterfaceError, DatabaseError) as error:
# display the error
print(error)
Although lines 36–41 should have displayed an error message indicating that the connection to the DBMS failed, nothing is displayed. In fact, upon further investigation, we see that the code does indeed enter the [except] block in lines 35–37, but the [error] variable is set to [None]. This occurs with version 2.8.4 of the [psycopg2] connector.
We can work around this problem by writing a generic but less precise message:
# connecting a non-existent user
try:
connection(host=HOST, login="xx", pwd="yy", database=DATABASE)
except (InterfaceError, DatabaseError) as error:
# display the error
print(f"Error connecting to database [{DATABASE}] by user [xx/yy]")
The results are as follows:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/databases/postgresql/pgres_02.py
Connection successful to database=dbpersonnes, host=localhost under user=admpersonnes, passwd=nobody
Disconnected successfully
Error connecting to the database [dbpersonnes] by user [xx/yy]
Process finished with exit code 0
17.4.4. script [pgres_03]
The script [pgres_03] is a copy of the script [mysql_03] (see section |script [mysql-03]: creating a MySQL table|). The following changes are made to it:
Instead of:
from mysql.connector import DatabaseError, InterfaceError, connect
from mysql.connector.connection import MySQLConnection
we write:
from psycopg2 import DatabaseError, InterfaceError, connect
from psycopg2.extensions import connection
Additionally, the signature of the [execute_sql] function, which was:
def execute_sql(connection: MySQLConnection, update: str):
becomes:
def execute_sql(connection: connection, update: str):
The rest remains unchanged. The result is as follows:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/databases/postgresql/pgres_03.py
create table people (id int PRIMARY KEY, first_name varchar(30) NOT NULL, last_name varchar(30) NOT NULL, age integer NOT NULL, unique(last_name, first_name)) : query successful
Process finished with exit code 0
You can verify the existence of the [people] table using the [pgAdmin] administration tool:

17.4.5. script [pgres_04]
The [pgres_04] script is a copy of the [mysql_04] script (see section |script [mysql-04]: executing an SQL command file|). It uses the [pgres_module] module:
# retrieve the application configuration
import config_04
config = config_04.configure()
# the syspath is configured—we can now perform imports
import sys
from pgres_module import execute_file_of_commands
from psycopg2 import connect, DatabaseError, InterfaceError
The rest remains unchanged.
We create a configuration [pgres pgres-04 without_transaction] as was done in the section |script [mysql-04]: executing an SQL command file|. We also create a configuration [pgres pgres-04 with_transaction].
Executing the [pgres pgres-04 without_transaction] configuration yields the following results:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/databases/postgresql/pgres_04.py false
--------------------------------------------------------------------
Executing the SQL file C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\databases\postgresql/data/commandes.sql without a transaction
--------------------------------------------------------------------
[drop table if exists personnes]: Execution successful
number of rows modified: -1
[create table people (id int primary key, first_name varchar(30) not null, last_name varchar(30) not null, age integer not null, unique (last_name, first_name))] : Execution successful
number of rows modified: -1
[insert into people(id, first_name, last_name, age) values(1, 'Paul','Langevin',48)] : Execution successful
Number of rows modified: 1
[insert into people(id, first_name, last_name, age) values (2, 'Sylvie','Lefur',70)] : Execution successful
Number of rows modified: 1
[select first_name, last_name, age from people]: Execution successful
first_name, last_name, age,
*****************
('Paul', 'Langevin', 48)
('Sylvie', 'Lefur', 70)
*****************
xx: Error (ERROR: syntax error at or near "xx"
LINE 1: xx
^
)
[insert into people(id, first_name, last_name, age) values (3, 'Pierre','Nicazou',35)] : Execution successful
number of rows modified: 1
[insert into people(id, first_name, last_name, age) values (4, 'Geraldine','Colou',26)] : Execution successful
Number of rows modified: 1
[insert into people(id, first_name, last_name, age) values (5, 'Paulette','Girond',56)] : Execution successful
number of rows modified: 1
[select first_name, last_name, age from people]: Execution successful
first_name, last_name, age,
*****************
('Paul', 'Langevin', 48)
('Sylvie', 'Lefur', 70)
('Pierre', 'Nicazou', 35)
('Geraldine', 'Colou', 26)
('Paulette', 'Girond', 56)
*****************
[select last_name, first_name from people order by last_name asc, first_name desc] : Execution successful
last_name, first_name,
************
('Colou', 'Geraldine')
('Girond', 'Paulette')
('Langevin', 'Paul')
('Lefur', 'Sylvie')
('Nicazou', 'Pierre')
************
[select last_name, first_name, age from people where age between 20 and 40 order by age desc, last_name asc, first_name asc] : Execution successful
last_name, first_name, age,
*****************
('Nicazou', 'Pierre', 35)
('Colou', 'Geraldine', 26)
*****************
[insert into people(id, first_name, last_name, age) values(6, 'Josette','Bruneau',46)] : Execution successful
number of rows modified: 1
[update people set age=47 where last_name='Bruneau']: Execution successful
number of rows modified: 1
[select last_name, first_name, age from people where last_name='Bruneau']: Execution successful
last_name, first_name, age,
*****************
('Bruneau', 'Josette', 47)
*****************
[delete from people where last_name='Bruneau']: Execution successful
number of rows modified: 1
[select last_name, first_name, age from people where last_name='Bruneau']: Execution successful
last_name, first_name, age,
*****************
*****************
--------------------------------------------------------------------
Execution complete
--------------------------------------------------------------------
There was 1 error
xx: Error (ERROR: syntax error at or near "xx"
LINE 1: xx
^
)
Process finished with exit code 0
- Line 5: We had to modify the command to drop the [people] table. Unlike the MySQL connector, the PostgreSQL connector throws an exception if the table to be dropped does not exist. The [drop table] command has a variant, [drop table if exists], which does not throw an exception if the table does not exist. We used it here. This is an example where two DBMSs do not behave the same way in similar situations;
The [people] table in the [pgAdmin] tool is as follows:

Running the configuration [pgres pgres_04 with_transaction] yields the following results:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/databases/postgresql/pgres_04.py true
--------------------------------------------------------------------
Executing the SQL file C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\databases\postgresql/data/commandes.sql with a transaction
--------------------------------------------------------------------
[drop table if exists personnes]: Execution successful
number of rows modified: -1
[create table people (id int primary key, first_name varchar(30) not null, last_name varchar(30) not null, age integer not null, unique (last_name, first_name))] : Execution successful
number of rows modified: -1
[insert into people(id, first_name, last_name, age) values(1, 'Paul','Langevin',48)] : Execution successful
Number of rows modified: 1
[insert into people(id, first_name, last_name, age) values (2, 'Sylvie','Lefur',70)] : Execution successful
number of rows modified: 1
[select first_name, last_name, age from people]: Execution successful
first_name, last_name, age,
*****************
('Paul', 'Langevin', 48)
('Sylvie', 'Lefur', 70)
*****************
xx: Error (ERROR: syntax error at or near "xx"
LINE 1: xx
^
)
--------------------------------------------------------------------
Execution complete
--------------------------------------------------------------------
There was 1 error(s)
xx: Error (ERROR: syntax error at or near "xx"
LINE 1: xx
^
)
Process finished with exit code 0
The [people] table in the [pgAdmin] tool is as follows:

Here, the result differs from that obtained with MySQL. If we run the scripts under the same conditions—i.e., after running the script without a transaction—we get the following results:
- With MySQL, the [people] table is empty;
- with PostgreSQL, the [people] table is not empty;
The difference lies in the different ways these two DBMSs roll back the transaction:
- MySQL does not roll back the [drop table] and [create table] commands. We end up with an empty [people] table;
- PostgreSQL rolls back the [drop table] and [create table] commands. The table is restored to the state it was in before the script was executed with a transaction;
17.4.6. script [pgres_05]
The script [pgres_05] is a copy of the script [mysql_05] (see section |script [mysql-05]: using parameterized queries|). The script is modified as follows:
Instead of:
# imports
from mysql.connector import connect, DatabaseError, InterfaceError
we write:
# imports
from psycopg2 import connect, DatabaseError, InterfaceError
The rest remains unchanged.
The results obtained in [pgAdmin] are as follows:

17.5. Conclusion
Porting the MySQL scripts to PostgreSQL scripts was relatively easy. This is an exception. The two DBMSs do not support the same naming conventions for SQL objects (databases, tables, columns, constraints, data types, etc.), and have incompatible SQL extensions… To ensure a simple port, one must adhere to the SQL standard in both cases without attempting to use the DBMS’s proprietary extensions. This comes at the expense of performance.