Skip to article frontmatterSkip to article content

Adapted Attribute Types

Purpose: demonstrate using dj.AttributeAdapter for convenient storage of arbitrary data types in DataJoint table attributes.

Imagine I want store graph objects of type networkx.Graph in the form of edge lists.

First, let’s create a few graphs:

%matplotlib inline
from matplotlib import pyplot as plt
import networkx as nx
graphs = [nx.lollipop_graph(4, 2), nx.star_graph(5), nx.barbell_graph(3, 1), nx.cycle_graph(5)]

fig, axx = plt.subplots(1, len(graphs) , figsize=(15, 3))
for g, ax in zip(graphs, axx.flatten()):
    plt.sca(ax)
    nx.draw(g)
<Figure size 1080x216 with 4 Axes>
import numpy as np

Then we need to define an adapter object that convert target objects into an attribute type that datajoint can already store. The class must subclass dj.AttributeAdapter and define the property attribute_type, and methods get and put. These methods translate the adapted data type nx.Graph into a representation that can be stored in datajoint, a longblob storing the edge list.

import datajoint as dj

class GraphAdapter(dj.AttributeAdapter):
    
    attribute_type = 'longblob'   # this is how the attribute will be declared
    
    def put(self, obj):
        # convert the nx.Graph object  into an edge list
        assert isinstance(obj, nx.Graph)
        return list(obj.edges)

    def get(self, value):
        # convert edge list back into an nx.Graph
        return nx.Graph(value)
    

# instantiate for use as a datajoint type
graph = GraphAdapter()

Now we can define a table that uses graph as its attribute type. These “adapted types” must be enclosed in angle brackets as in <graph>:

schema = dj.schema('test_graphs')
schema.drop()  # drop previous contents
schema = dj.schema('test_graphs')  # create de novo
Connecting dbadmin@dimitri-proj0.cda95qzjbnvs.us-east-1.rds.amazonaws.com:3306
Proceed to delete entire schema `test_graphs`? [yes, No]: yes
# The following is necessary in DataJoint version 0.12.* 
# while adapted types are in beta testing.
dj.errors._switch_adapted_types(True)  
@schema
class Connectivity(dj.Manual):
    definition = """
    conn_id : int
    ---
    conn_graph = null : <graph>  # a networkx.Graph object 
    """
Connectivity.describe();
conn_id              : int                          
---
conn_graph=null      : <graph>                      # a networkx.Graph object

Now, populate the table with our example graphs and fetch them as objects

Inserting the graphs as objects

Connectivity.insert((i, g) for i, g in enumerate(graphs))

We can now fetch the graphs as an array of objects and plot them to verify successful recovery.

result = Connectivity.fetch('conn_graph', order_by='conn_id')

fig, axx = plt.subplots(1, result.size, figsize=(15, 3))
for g, ax in zip(result, axx.flatten()):
    plt.sca(ax)
    nx.draw(g)
<Figure size 1080x216 with 4 Axes>