Hands-on with Remodel: a new Python ODM for RethinkDB

This week, Andrei Horak released Remodel, a new Python-based object document mapping (ODM) library for RethinkDB. Remodel simplifies RethinkDB application development by automating much of the underlying logic that comes into play when working with relations.

Remodel users create high-level model objects and rely on a set of simple class attributes to define relationships. The framework then uses the model objects to generate tables and indexes. It abstracts away the need to do manual work like performing join queries or populating relation attributes when inserting new items. Remodel also has built-in support for connection pooling, which obviates the need to create and manage connections. In this brief tutorial, I’ll give you a hands-on look at Remodel and show you how to use it in a web application.

Define your models

To start using Remodel, first install the library. You can use the setup.py included in the source code or you can install it from pip by typing pip install remodel at the command line.

For the purposes of this tutorial, let’s assume that we want to build a Starfleet crew roster that correlates crew members with their starships. The first step is to define the models and create the tables:

import remodel.utils
import remodel.connection
from remodel.models import Model

remodel.connection.pool.configure(db="fleet")

class Starship(Model):
    has_many = ("Crewmember",)

class Crewmember(Model):
    belongs_to = ("Starship",)

remodel.utils.create_tables()
remodel.utils.create_indexes()

In an application built with Remodel, all of the model classes must inherit remodel.models.Model. In this application, there are two models: Starship and Crewmember. The has_many and belongs_to class attributes are used to define the relationships between objects. In this case, each Starship can have many Crewmember instances and each Crewmember instance belongs to only one Starship.

The create_tables and create_indexes methods will, as the names suggest, automatically generate tables and indexes based on your defined models. Remodel pluralizes your table names, which means that the Starship model will get a starships table.

The framework instantiates a connection pool, accessible at remodel.connection.pool. You can use the pool’s configure method to adjust its behavior and specify connection options, such as the desired database name, host, and port.

Populate the database

Now that the models are defined, you can populate the database with content. To create a new database record, call the create method on one of the model classes:

voyager = Starship.create(name="Voyager", category="Intrepid", registry="NCC-74656")

Remodel doesn’t enforce any schemas, so you can use whatever properties you want when you create a record. The create method used above will automatically add the Voyager record to the starships table. Because the Starship model defines a has_many relationship with the Crewmember model, the voyager record comes with a crewmembers property that you can use to access the collection of crew members that are associated with the ship. Use the following code to add new crew members:

voyager["crewmembers"].add(
    Crewmember(name="Janeway", rank="Captain", species="Human"),
    Crewmember(name="Neelix", rank="Morale Officer", species="Talaxian"),
    Crewmember(name="Tuvok", rank="Lt Commander", species="Vulcan"))

The records provided to the add method are instantiated directly from the Crewmember class. You don’t want to use the create method in this case because the add method called on the Voyager instance handles the actual database insertion. It will also automatically populate the relation data, adding a starship_id property to each Crewmember record.

To make the example more interesting, add a few more Starship records to the database:

enterprise = Starship.create(name="Enterprise", category="Galaxy", registry="NCC-1701-D")
enterprise["crewmembers"].add(
    Crewmember(name="Picard", rank="Captain", species="Human"),
    Crewmember(name="Data", rank="Lt Commander", species="Android"),
    Crewmember(name="Troi", rank="Counselor", species="Betazed"))

defiant = Starship.create(name="Defiant", category="Defiant", registry="NX-74205")
defiant["crewmembers"].add(
    Crewmember(name="Sisko", rank="Captain", species="Human"),
    Crewmember(name="Dax", rank="Lt Commander", species="Trill"),
    Crewmember(name="Kira", rank="Major", species="Bajoran"))

Query the database

When you want to retrieve a record, you can invoke the get method on a model class. When you call the get method, you can either provide the ID of the specific record that you want or you can provide keyword arguments that perform a query against record attributes. If you want to get a specific starship by name, for example, you can do the following:

voyager = Starship.get(name="Voyager")

You can take advantage of the relations that you defined in your models. If you want to find all of the human members of Voyager’s crew, you can simply use the filter method on the crewmembers property:

voyager = Starship.get(name="Voyager")
for human in voyager["crewmembers"].filter(species="Human"):
  print human["name"]

Perform filtering on an entire table by calling the filter method on a model class. The following code shows how to display the captain of each ship:

for person in Crewmember.filter(rank="Captain"):
  print person["name"], "captain of", person["starship"]["name"]

As you might have noticed, the starship property of the Crewmember instance points to the actual starship record. Remodel populates the property automatically to handle the Crewmember model’s belongs_to relationship.

When you want to perform more sophisticated queries, you can use ReQL in conjunction with Remodel. Let’s say that you want to evaluate Starfleet’s diversity by determining how many crew members are of each species. You can use ReQL’s group command:

Crewmember.table.group("species").ungroup() \
          .map(lambda item: [item["group"], item["reduction"].count()]) \
          .coerce_to("object").run()

The table property of a model class provides the equivalent of a ReQL r.table expression. You can chain additional ReQL commands to the table property just as you would when creating any ReQL query.

Put it all together

Just for fun, I’m going to show you how to build a web application for browsing the Starfleet crew roster. The app is built with Flask, a lightweight framework for web application development. The example also uses Jinja, a popular server-side templating system that is commonly used with Flask.

In a Flask application, the developer defines URL routes that are responsible for displaying specific kinds of information. The application uses templates to render the data in HTML format. Create a route at the application root:

app = flask.Flask(__name__)

@app.route("/")
def ships():
    return flask.render_template("ships.html", ships=Starship.all())

if __name__ == "__main__":
    app.run(host="localhost", port=8090, debug=True)

When the user visits the site root, the application will fetch all of the starships from the database and display them by rendering the ships.html template. The following is from the template file:

<ul>
  {% for ship in ships %}
  <li><a href="/ship/{{ ship.id }}">{{ ship.name }}</a></li>
  {% endfor %}
</ul>

In the example above, the template iterates over every ship and displays a list item for each one. The list item includes an anchor tag that points to a URL with the ship’s ID.

To make the application display the crew members of the ship when the user clicks one of the links, create a new /ship/x route that takes an arbitrary ship ID as a parameter:

@app.route("/ship/<ship_id>")
def ship(ship_id):
    ship = Starship.get(ship_id)
    crew = ship["crewmembers"].all()
    return flask.render_template("ship.html", ship=ship, crew=crew)

Fetch the desired ship from the database using the provided ID. In a real-world application, you might want to check to make sure that the record exists and throw an error if it doesn’t. Once you have the ship, fetch the crew via the crewmembers property. Pass both the ship and the crew to the template:

<h1>{{ ship.name }}</h1>
<ul>
  {% for member in crew %}
  <li><a href="/member/{{ member.id }}">{{ member.name }}</a></li>
  {% endfor %}
</ul>

Now create a /member/x route so that the user can see additional information about a crewman when they click one in the list:

@app.route("/member/<member_id>")
def member(member_id):
    member = Crewmember.get(member_id)
    return flask.render_template("crew.html", member=member)

Finally, define the template for that route:

<h1>{{ member.name }}</h1>
<ul>
  <li><strong>Rank:</strong> {{ member.rank }}</li>
  <li><strong>Species:</strong> {{ member.species }}</li>
</ul>

The template HTML files should go in a template folder alongside your Python script. When you run the Python script, it will start a Flask server at the desired port. You should be able to visit the URL and see the application in action.

Check out Remodel and Install RethinkDB to try it for yourself.

Resources