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
- Remodel source code on GitHub
- Flask web application framework
- The RethinkDB 10-minute guide