Python sqlite3: How to register adapters and converters

Updated: February 6, 2024 By: Guest Contributor Post a comment

Overview

SQLite is a C library that provides a lightweight, disk-based database that doesn’t require a separate server process. In Python, the sqlite3 module provides an interface to work with SQLite databases. One of the powerful features of the sqlite3 module is its ability to convert Python types to SQLite types and vice versa. This is achieved through adapters and converters. In this tutorial, we’ll explore how to register and use these adapters and converters in Python.

Understanding Adapters and Converters

Adapters allow Python values to be encoded as SQLite data types. Conversely, converters let you turn SQLite types back into Python types when data is fetched from the database. This translation is crucial for ensuring data integrity and seamless Python-SQLite integration.

Basic Setup

Before we dive into adapters and converters, let’s set up a basic SQLite database:

import sqlite3

def setup_db():
    conn = sqlite3.connect('example.db')
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS records (id INTEGER PRIMARY KEY, info TEXT)''')
    conn.commit()
    conn.close()

setup_db()

This code creates a database file named example.db with a single table, records.

Registering Adapters

Let’s begin by encoding Python dictionary objects into JSON strings before inserting them into the database:

import json

sqlite3.register_adapter(dict, lambda d: json.dumps(d).encode())

Here, we’ve registered an adapter that converts Python dict objects into JSON strings, subsequently encoded into bytes for SQLite.

Inserting Data Using Adapters

With our adapter in place, let’s insert a dictionary into the records table:

def insert_dict(conn, info):
    c = conn.cursor()
    c.execute("""INSERT INTO records (info) VALUES (?)""", (info,))
    conn.commit()

conn = sqlite3.connect('example.db')
insert_dict(conn, {'key': 'value'})
conn.close()

This demonstrates how the adapter automatically converts the dictionary to a JSON string during the insertion process.

Registering Converters

To retrieve our data and automatically convert it back into a Python dictionary, we must register a converter:

sqlite3.register_converter("JSON", lambda x: json.loads(x.decode()))

We registered a converter named “JSON” that decodes the stored byte string into a Python dictionary using JSON.

Using Converters

To use our converter, we need to modify how we connect to the database:

conn = sqlite3.connect('example.db', detect_types=sqlite3.PARSE_DECLTYPES)

This tells sqlite3 to use declared types in the database schema for type detection. Then, we update our table to use the custom “JSON” datatype:

conn.execute('ALTER TABLE records ADD COLUMN json_info JSON')
conn.commit()

Now, let’s insert data into the new column and fetch it back:

def insert_and_fetch_json(conn):
    conn.execute("""INSERT INTO records (json_info) VALUES (?)""", ("{'json_key': 'json_value'}",))
    for row in conn.execute("SELECT json_info FROM records"):
        print(row[0])

conn = sqlite3.connect('example.db', detect_types=sqlite3.PARSE_DECLTYPES)
insert_and_fetch_json(conn)
conn.close()

When fetched, the json_info is automatically converted back into a Python dictionary.

Custom Types

The real power of adapters and converters lies in their ability to handle custom Python types. Let’s create a simple class and see how it can be stored and retrieved from the database:

class MyData:
    def __init__(self, data):
        self.data = data

    def __repr__(self):
        return f'MyData({self.data})'

sqlite3.register_adapter(MyData, lambda x: x.data.encode())
sqlite3.register_converter('MyDataType', lambda x: MyData(x.decode()))

Here we defined both the adapter and converter for our MyData class, allowing its instances to be stored and fetched seamlessly.

Advanced Usage

You can further customize the handling of your data types in SQLite by manipulating data during the adapter and converter processes. This customization opens possibilities for efficient data storage and retrieval mechanisms bespoke to your application needs.

Conclusion

Registering adapters and converters in Python 3’s sqlite3 module enables seamless integration of complex Python data types with SQLite databases. This tutorial has showcased basic to advanced usage, underlying the importance and versatility of these features. With this knowledge, you can ensure your application leverages Python and SQLite to their fullest potentials.