📜 ⬆️ ⬇️

Python: Working with a Database, Part 1/2: Using DB-API

Python DB-API is not a specific library, but a set of rules that govern individual modules that implement work with specific databases. The individual implementation nuances for different databases may differ, but the general principles allow using the same approach when working with different databases.

The article discusses the basic methods of DB-API, allowing you to fully work with the database. The full list can be found on the links at the end of the article.

Required level of preparation : a basic understanding of the syntax of SQL and Python.

Preparing inventory for further comfortable work



Python DB-API modules depending on the database

DatabaseDB-API module
Sqlitesqlite3
PostgreSQLpsycopg2
Mysqlmysql.connector
Odbcpyodbc

Connection to the base, getting the cursor


To begin, consider the most basic DB-API template, which we will use in all further examples:

 #  ,      import sqlite3 #       #          conn = sqlite3.connect('Chinook_Sqlite.sqlite') #   -           cursor = conn.cursor() #         #        #        conn.close() 

When working with other databases, additional connection parameters are used, for example, for PostrgeSQL:

 conn = psycopg2.connect( host=hostname, user=username, password=password, dbname=database) 

Reading from base


 #  SELECT    ,   SQL- cursor.execute("SELECT Name FROM Artist ORDER BY Name LIMIT 3") #     results = cursor.fetchall() results2 = cursor.fetchall() print(results) # [('A Cor Do Som',), ('Aaron Copland & London Symphony Orchestra',), ('Aaron Goldberg',)] print(results2) # [] 

Please note: After getting the result from the cursor, the second time without repeating the query itself you cannot get it - the empty result will return!

Record to base


 #  INSERT    ,   SQL- cursor.execute("insert into Artist values (Null, 'A Aagrh!') ") #     ,        -    conn.commit() #   cursor.execute("SELECT Name FROM Artist ORDER BY Name LIMIT 3") results = cursor.fetchall() print(results) # [('A Aagrh!',), ('A Cor Do Som',), ('Aaron Copland & London Symphony Orchestra',)] 

Note : If several connections are made to the database and one of them performs a modification of the base, the SQLite database is locked before completion (connection method. Commit ()) or cancellation (connection method. Rollback ()) of the transaction.

We split the query into multiple lines in triple quotes


Long queries can be split into several lines in random order, if they are enclosed in triple quotes - single ('' '...' '') or double ("" "..." "")

 cursor.execute(""" SELECT name FROM Artist ORDER BY Name LIMIT 3 """) 

Of course, in such a simple example the breakdown does not make sense, but on complex long queries it can dramatically increase the readability of the code.

Combining database queries into one method call


The cursor method .execute () allows you to do only one query at a time, when you try to make multiple semicolons, there will be an error.

For those who do not believe in the word:
 cursor.execute(""" insert into Artist values (Null, 'A Aagrh!'); insert into Artist values (Null, 'A Aagrh-2!'); """) # sqlite3.Warning: You can only execute one statement at a time. 

To solve this problem, you can either call the cursor method several times. execute ()

 cursor.execute("""insert into Artist values (Null, 'A Aagrh!');""") cursor.execute("""insert into Artist values (Null, 'A Aagrh-2!');""") 

Or use the cursor method. executescript ()

 cursor.executescript(""" insert into Artist values (Null, 'A Aagrh!'); insert into Artist values (Null, 'A Aagrh-2!'); """) 

This method is also convenient when our requests are stored in a separate variable or even in a file and we need to apply it to the database.

Make the substitution of the value in the query


It is important ! Never, under any circumstances, use string concatenation (+) or parameter interpolation in a string (%) to transfer variables to a SQL query. Such a query formation, with the possibility of user data entering into it, is a gateway for SQL injections!

The correct way is to use the second argument of the .execute () method.

Two options are possible:

 # C       : cursor.execute("SELECT Name FROM Artist ORDER BY Name LIMIT ?", ('2')) #     : cursor.execute("SELECT Name from Artist ORDER BY Name LIMIT :limit", {"limit": 3}) 

Note 1 : In PostgreSQL (UPD: and in MySQL) instead of the '?' used for substitution:% s

Note 2 : In this way, it will not be possible to replace the names of the tables, one of the possible solutions in this case is considered here: stackoverflow.com/questions/3247183/variable-table-name-in-sqlite/3247553#3247553

UPD: Note 3 : Thanks to Igelko for mentioning the paramstyle parameter - it determines which style is used for variable substitution in this module.
Here is a link with a useful technique for working with different styles of substitutions .

We do multiple insertion of rows passing through the collection using the cursor method. executemany ()


 #  ,     -    ! #        ! new_artists = [ ('A Aagrh!',), ('A Aagrh!-2',), ('A Aagrh!-3',), ] cursor.executemany("insert into Artist values (Null, ?);", new_artists) 

We get the results one by one using the cursor method. fetchone ()


He always returns a tuple or None. if the request is empty.

 cursor.execute("SELECT Name FROM Artist ORDER BY Name LIMIT 3") print(cursor.fetchone()) # ('A Cor Do Som',) print(cursor.fetchone()) # ('Aaron Copland & London Symphony Orchestra',) print(cursor.fetchone()) # ('Aaron Goldberg',) print(cursor.fetchone()) # None 

It is important ! The standard cursor retrieves all data from the server immediately, regardless of whether we use .fetchall () or .fetchone ()

Cursor as an iterator


 #     for row in cursor.execute('SELECT Name from Artist ORDER BY Name LIMIT 3'): print(row) # ('A Cor Do Som',) # ('Aaron Copland & London Symphony Orchestra',) # ('Aaron Goldberg',) 

UPD: Increase code stability


I thank paratagas for the valuable addition:
To make the program more stable (especially during write operations), you can wrap the instructions for accessing the database in “try-except-else” blocks and use the native error object built into sqlite3, for example, like this:
 try: cursor.execute(sql_statement) result = cursor.fetchall() except sqlite3.DatabaseError as err: print("Error: ", err) else: conn.commit() 

UPD: Using with psycopg2


Thanks to KurtRotzke for the valuable addition:
Recent versions of psycopg2 allow you to do this:
 with psycopg2.connect("dbname='habr'") as conn: with conn.cursor() as cur: 

Some objects in Python have __enter__ and __exit__ methods, which allows you to "cleanly" interact with them, as in the example above.

UPD: row_factory usage


Thanks to remzalp for a valuable addition:
Using row_factory allows you to take metadata from a query and refer to the result, for example, by the column name.
Essentially - a callback to process data when returning a string. Yes, and a useful cursor.description, where there is everything you need.

Example from documentation:
 import sqlite3 def dict_factory(cursor, row): d = {} for idx, col in enumerate(cursor.description): d[col[0]] = row[idx] return d con = sqlite3.connect(":memory:") con.row_factory = dict_factory cur = con.cursor() cur.execute("select 1 as a") print(cur.fetchone()["a"]) 


Additional materials (in English)





The second part of the article is under development, where working with the database in Python using SQLAlchemy will be considered.

I invite you to discuss:


Source: https://habr.com/ru/post/321510/


All Articles