| # Simple example presenting how persistent ID can be used to pickle |
| # external objects by reference. |
| |
| import pickle |
| import sqlite3 |
| from collections import namedtuple |
| |
| # Simple class representing a record in our database. |
| MemoRecord = namedtuple("MemoRecord", "key, task") |
| |
| class DBPickler(pickle.Pickler): |
| |
| def persistent_id(self, obj): |
| # Instead of pickling MemoRecord as a regular class instance, we emit a |
| # persistent ID. |
| if isinstance(obj, MemoRecord): |
| # Here, our persistent ID is simply a tuple, containing a tag and a |
| # key, which refers to a specific record in the database. |
| return ("MemoRecord", obj.key) |
| else: |
| # If obj does not have a persistent ID, return None. This means obj |
| # needs to be pickled as usual. |
| return None |
| |
| |
| class DBUnpickler(pickle.Unpickler): |
| |
| def __init__(self, file, connection): |
| super().__init__(file) |
| self.connection = connection |
| |
| def persistent_load(self, pid): |
| # This method is invoked whenever a persistent ID is encountered. |
| # Here, pid is the tuple returned by DBPickler. |
| cursor = self.connection.cursor() |
| type_tag, key_id = pid |
| if type_tag == "MemoRecord": |
| # Fetch the referenced record from the database and return it. |
| cursor.execute("SELECT * FROM memos WHERE key=?", (str(key_id),)) |
| key, task = cursor.fetchone() |
| return MemoRecord(key, task) |
| else: |
| # Always raises an error if you cannot return the correct object. |
| # Otherwise, the unpickler will think None is the object referenced |
| # by the persistent ID. |
| raise pickle.UnpicklingError("unsupported persistent object") |
| |
| |
| def main(): |
| import io |
| import pprint |
| |
| # Initialize and populate our database. |
| conn = sqlite3.connect(":memory:") |
| cursor = conn.cursor() |
| cursor.execute("CREATE TABLE memos(key INTEGER PRIMARY KEY, task TEXT)") |
| tasks = ( |
| 'give food to fish', |
| 'prepare group meeting', |
| 'fight with a zebra', |
| ) |
| for task in tasks: |
| cursor.execute("INSERT INTO memos VALUES(NULL, ?)", (task,)) |
| |
| # Fetch the records to be pickled. |
| cursor.execute("SELECT * FROM memos") |
| memos = [MemoRecord(key, task) for key, task in cursor] |
| # Save the records using our custom DBPickler. |
| file = io.BytesIO() |
| DBPickler(file).dump(memos) |
| |
| print("Pickled records:") |
| pprint.pprint(memos) |
| |
| # Update a record, just for good measure. |
| cursor.execute("UPDATE memos SET task='learn italian' WHERE key=1") |
| |
| # Load the records from the pickle data stream. |
| file.seek(0) |
| memos = DBUnpickler(file, conn).load() |
| |
| print("Unpickled records:") |
| pprint.pprint(memos) |
| |
| |
| if __name__ == '__main__': |
| main() |