Quickstart

Bulbs is a Python persistence framework for graph databases.

There are 3 different ways to use Bulbs:

  1. Graph library to connect to databases through Rexster.
  2. Persistence framework to model and interact with domain objects.
  3. LinkedData client to tap remote resources on the Web of Data.

This Quickstart will show you how to get everything up and running and basic examples of how to use Bulbs.

Before you begin, make sure you download and install Bulbs and Rexster.

Rexster

Bulbs’ Rexster Resource is a Python interface to the Rexster API.

Rexster is a REST server for graph databases with direct support for searching, ranking, scoring, and more specifically, recommendation. See the Rexster documenation for details.

To use Bulbs with Rexster, you need to make sure Rexster is up and running.

Start Rexster

First, open up a new terminal because we’re going to let Rexster run in the foreground.

Then change to the directory where you installed Rexster, and start it with its default configuration:

$ cd rexster
$ ./rexster.sh --start

By default, Rexster is configured with a few sample graph databases, including an in-memory TinkerGraph – this is the database will use for most of these examples.

Browse Rexster

Now check to see that you can browse the TinkerGraph database using these URLs:

  • http://localhost:8182/graphs/tinkergraph/indices/vertices?key=name&value=marko

If you can view these, then success! Rexster is up and running.

Note

Rexster returns JSON documents, and Chrome lets you view JSON in the browser. However, if you’re on Firefox, you may need to install the JSONView Add On; otherwise, Firefox will prompt you to download the file.

Browse DogHouse

You should also be able to access DogHouse:

DogHouse is a browser-based interface to Rexster that allows you to view elements in a graph and simulate a Gremlin-console session. See the DogHouse documentation for details.

Using DogHouse is sometimes convienent, but most of the time I use the Web browser to browse the JSON documents directly and the external Gremlin REPL to experiment with queries.

Configure Rexster

Configuring Rexster is easy. Simply copy the default rexster.xml and edit it:

$ cd rexster
$ mkdir config
$ cp rexster.xml config
$ emacs config/rexster.xml

For example, to configure Rexster to use Neo4j (you don’t need to do this now):

  1. Open rexster.xml.
  2. Copy the <graph> section labeled neo4jsample.
  3. Paste it at the top of the <graphs> (plural) section.
  4. Change the graph-name to a URL-friendly name you want to reference it by.
  5. Set graph-enabled to true.
  6. Set graph-file to the directory where you want to store Neo4j.
  7. Configure the remaining parameters as desired (this is not required).
  8. Save and close rexster.xml.

See the Rexster configuration page for details.

Here’s how to start Rexster using your custom configuration:

$ ./rexster.sh --start -c config/rexster.xml

Graph Interface

Ok, now the fun part. This will show you how to use Bulbs to connect to Rexster from Python and interact with graph databases.

Create a Graph Object

By default Bulbs is configured to use TinkerPop, an in-memory database with some preloaded data for testing (the same one you viewed in your browser when you started Rexster).

Here’s how you create a graph object, your primary interface to Rexster:

>>> from bulbs.graph import Graph
>>> g = Graph()

If you don’t supply an argument to Graph(), the graph object will contain a reference to the default database, which you can change in the Bulbs config file.

Display Vertices

Display all the vertices in the graph:

>>> g.V

This returns a list of dicts containg all the vertices in the graph.

This method is primarily used for testing because you won’t normally want to do this for a large database containing millions (or billions) of vertices.

You can see how many vertices were returned by checking the length of the list:

>>> vertices = g.V
>>> len(vertices)

As you can see, the default TinkerGraph database has been automatically loaded with some test data and contains 6 vertices.

Display Edges

Display all the edges in the graph:

>>> g.E

Like g.V, this method is primarily used for testing because you won’t normally want to do this for a large database.

Create Vertices and Edges

Here is a basic example that creates two Vertex objects and an Edge that links them together:

>>> james = g.vertices.create({'name':'James'})
>>> julie = g.vertices.create({'name':'Julie'})
>>> g.edges.create(james,"knows",julie)

Now check to see that the vertices and edge were added to the graph:

>>> g.V
>>> g.E

Look Up Indexed Vertices

Rexster automatically creates 2 generic indexes for you – one for vertices and one for edges.

Everytime you create a new vertex, it’s automatically added to vertex index and you can look it up by its properties:.

This will return all the vertices that have a “name” property with a value of “marko”:

>>> g.idxV(name="james")

As you can see, the return value is a generator.

A generator is iterable like a list, but it’s more memory friendly because it’s not copying the entire list around – it lazy loads items as needed:

>>> vertices = g.idxV(name="james")
>>> for vertex in vertices: print vertex

To convert a generator into a list, do something like this:

>>> vertices = g.idxV(name="james")
>>> list(vertices)

Notice this list contains a Vertex object instead of a dict like g.V returns.

By default idxV() will return a list of initialized Vertex objects, but you can tell it not to and get the raw Response object by setting raw=True:

>>> resp = g.idxV(name="james",raw=True)
>>> list(resp.results)

resp.results is an iterator (iter(), which is like a generator) containing dicts.

Look Up Indexed Edges

Likewise everytime you create a new edge, it’s automatically added to the edge index and you can look it up by its properties.

This will return all the edges that have the label “created”:

>>> edges = g.idxE(label="knows")
>>> list(edges)

By default idxE() will return a list of initialized Edge objects, but you can tell it not to and get the raw Response object by setting raw=True:

>>> resp = g.idxE(label="knows",raw=True)
>>> list(resp.results)

resp.results is an iterator (iter(), which is like a generator) containing dicts.

Note

In normal code, you won’t be converting the iterators and genertors to lists – you’ll just iterate over the returned object. I’m converting them to lists here so you can see what they contain.

Note

g.V and g.E don’t return initialized objects because they’re primarily used for testing so it’s more useful to see the raw results.

Get Vertex by ID

>>> james._id
3
>>> james2 = g.vertices.get(3)
>>> assert james == james2

Update Vertex

>>> g.vertices.update(3, {'age':'34'})

Get Adjacent Edges

>>> james.outE()
>>> james.inE()
>>> james.bothE()

Get Adjacent Vertices

>>> james.outV()
>>> james.inV()
>>> james.bothV()

Connect to Multiple Databases

To connect to a database other than the default specified in the Bulbs config file, pass in a URL argument pointing to the desired Rexster database:

>>> from bulbs.graph import Graph
>>> default_graph = Graph()
>>> g = Graph('http://localhost:8182/gratefulgraph')

To make sure it’s working, try displaying all of the graph’s vertices:

>>> g.V

You can configure a new database by modifying Rexster’s config file (rexster.xml), which is located under the directory where you installed Rexster.

Models

Domain Models

You can also use Bulbs as a framework to model your domain objects:

from bulbs.model import Node
from bulbs.property import Property, String, Integer

class Person(Node):
    element_type = "person"

    name = Property(String, nullable=False)
    age = Property(Integer)

    def after_created():
        # include code to create relationships and to index the node
        pass

Domain Objects

The framework provides type checking and validatoin to ensure that your database properties are stored properly, and you can create “triggers” that execute before/after an element is created, read, updated, or deleted.

Here’s how you would use the Person model to create domain objects:

>>> from whybase.person import Person
>>> from bulbs.model import Relationship
>>>
>>> james = Person(name="James Thornton")
>>> james.eid
3
>>> james.name
'James Thornton'
>>>
>>> james = Person.get(3)
>>> james.age = 34
>>> james.save()
>>>
>>> rush = Person(name="Rush Vann")
>>>
>>> Relationship.create(james,"knows",rush)

LinkedData Client

Connect to LinkedData Stores

And through the OpenRDF sail interface, it can connect to remote LinkedData stores, such as DBpedia, Freebase, etc:

>>> from bulbs.graph import Graph
>>> g = Graph(' http://localhost:8182/sailgraph')
>>> v = g.v('http://data.semanticweb.org/person/christian-bizer')