Sling Academy
Home/Python/Python sqlite3: Serialize a database into bytes and deserialize it back

Python sqlite3: Serialize a database into bytes and deserialize it back

Last updated: February 06, 2024

Introduction

SQLite is a C-library that provides a lightweight, disk-based database. It doesn’t require a separate server process. Databases in SQLite are often used for local storage within applications. With Python’s built-in sqlite3 library, manipulating SQLite databases becomes intuitive and straightforward.

Serialization is the process of converting an object into a format that can be stored or transmitted and subsequently reconstructed. When it comes to databases, serialization involves converting the database into a byte array, while deserialization is the process of converting the byte array back into a database.

In this tutorial, you’ll learn how to serialize an SQLite database into bytes and then deserialize those bytes back into a database using Python 3.11. This technique is especially useful for scenarios such as caching, distributing databases across networks, or storing database snapshots in memory. We will cover this process in stages, gradually building up from basic concepts to more advanced techniques.

Getting Started

First, ensure you have Python 3.11 installed. We will be using the sqlite3 library that comes bundled with Python.

Basic Serialization

To begin, let’s create a simple SQLite database and serialize it.

import sqlite3
from io import BytesIO

# Create a new database in memory
db = sqlite3.connect(':memory:')
c = db.cursor()

# Create a simple table
c.execute('''CREATE TABLE test (id INTEGER PRIMARY KEY, name text)''')
# Insert some data
c.execute('''INSERT INTO test (name) VALUES ('Alice'), ('Bob')''')
db.commit()

# Serialize the database
db_file = BytesIO()
for line in db.iterdump():
    db_file.write(line.encode('utf-8'))
db.close()

# db_file now contains the serialized database
print(db_file.getvalue())

In the above example, we create a simple database with one table and insert two rows. We then serialize the database to a BytesIO object, effectively transferring the entire database into a bytes-like object. You can use db_file.getvalue() to retrieve the byte array.

Deserialization

To deserialize, we simply reverse the process.

from sqlite3 import connect
from io import BytesIO

# Assuming db_file is the BytesIO object with our serialized data
serialized_db = db_file.getvalue()

# Create a new database in memory and deserialize
new_db = connect(':memory:')
new_db.executescript(serialized_db.decode('utf-8'))
new_db.commit()

# Now, the new database contains the same data as the original
c = new_db.cursor()
c.execute('SELECT * FROM test')
print(list(c))

The deserialized database contains the same tables and data as the original. This demonstrates a basic serialization and deserialization process.

Advanced Serialization Techniques

Moving beyond basics, let’s consider more sophisticated use cases.

Compressing Serialized Data

Serialized databases can be large. Compression can make them more manageable.

import zlib

db_file.seek(0)  # Reset file pointer to the beginning
compressed_data = zlib.compress(db_file.getvalue())

# Compressed_data is now a smaller byte array
# Deserialize with decompression
compressed_db_file = BytesIO(zlib.decompress(compressed_data))
# Proceed with deserialization as above

Using zlib for compression and decompression can significantly reduce the size of the serialized data without losing any information. This method is particularly useful when bandwidth or storage efficiency is a concern.

Sending and Receiving Serialized Data Over a Network

Serialized data can be transmitted over networks. Here’s a basic example using Python’s socket library.

import socket
import zlib

# Assume 'compressed_data' is our serialized and compressed database
HOST = 'localhost'  # The server's hostname or IP address
PORT = 65432        # The port used by the server

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(compressed_data)
    data = s.recv(1024)

print('Received', repr(data))

This snippet sends the compressed database to a server, which could then decompress and deserialize it back into a SQLite database.

Serialization with Custom Data Types

SQLite and the sqlite3 library support custom data types. You may need special handling to serialize and deserialize these correctly. Here’s how you could encode and decode custom types.

from datetime import datetime
import sqlite3
import json

# Define a custom adapter and converter for datetime objects
def adapt_datetime(ts):
    return ts.timestamp()

def convert_datetime(timestamp):
    return datetime.fromtimestamp(float(timestamp))

sqlite3.register_adapter(datetime, adapt_datetime)
sqlite3.register_converter('timestamp', convert_datetime)

db = sqlite3.connect(':memory:', detect_types=sqlite3.PARSE_DECLTYPES)
c = db.cursor()
c.execute('''CREATE TABLE events (id INTEGER PRIMARY KEY, event_time timestamp)''')
c.execute('''INSERT INTO events (event_time) VALUES (?)''', (datetime.now(),))
db.commit()

# Serialization as described previously
# Deserialization needs to ensure the converter is registered as before

Using custom adapters and converters ensures your custom data types are correctly handled during the serialization and deserialization processes.

Conclusion

We’ve explored how to serialize and deserialize SQLite databases in Python, from simple techniques to more advanced scenarios including compression and working with custom data types. This functionality can greatly enhance the flexibility and portability of your applications.

Next Article: Python sqlite3: fetchone(), fetchmany(), and fetchall() methods – Examples

Previous Article: Python sqlite3 – iterdump() and backup() methods: Explanation with examples

Series: Data Persistence in Python – Tutorials & Examples

Python

You May Also Like

  • Python Warning: Secure coding is not enabled for restorable state
  • Python TypeError: write() argument must be str, not bytes
  • 4 ways to install Python modules on Windows without admin rights
  • Python TypeError: object of type ‘NoneType’ has no len()
  • Python: How to access command-line arguments (3 approaches)
  • Understanding ‘Never’ type in Python 3.11+ (5 examples)
  • Python: 3 Ways to Retrieve City/Country from IP Address
  • Using Type Aliases in Python: A Practical Guide (with Examples)
  • Python: Defining distinct types using NewType class
  • Using Optional Type in Python (explained with examples)
  • Python: How to Override Methods in Classes
  • Python: Define Generic Types for Lists of Nested Dictionaries
  • Python: Defining type for a list that can contain both numbers and strings
  • Using TypeGuard in Python (Python 3.10+)
  • Python: Using ‘NoReturn’ type with functions
  • Type Casting in Python: The Ultimate Guide (with Examples)
  • Python: Using type hints with class methods and properties
  • Python: Typing a function with default parameters
  • Python: Typing a function that can return multiple types