The Home Page

Now for another challenge.

In this challenge, you will use the knowledge gained so far in this course to add new functionality to the API. You will modify the all() method of the MovieDAO to do the following:

Once you have completed the challenge, you will be asked to run a unit test to verify that the code has been correctly implemented. If the test runs correctly, the title of the highest rated movie will be logged. You will need this value to verify that the test has run correctly.

Exploring the Code

Before you start, let’s take a look at the code. If you are not interested in exploring the code, you can skip straight to Implementing Read Transactions.

If you start the application and access the app at http://localhost:3000, you will see two lists on the home page; one for Popular Movies and one for Latest Releases. Both of these lists are populated by a request to http://localhost:3000/api/movies with some additional parameters.

Route Handler

You can find the route handler, the function that handles the request, in the movies blueprint at api/routes/movies.py:

python
api/routes/movie.py
@movie_routes.get('/')
@jwt_required(optional=True)
def get_movies():
    # Extract pagination values from the request
    sort = request.args.get("sort", "title")
    order = request.args.get("order", "ASC")
    limit = request.args.get("limit", 6, type=int)
    skip = request.args.get("skip", 0, type=int)

    # Get User ID from JWT Auth
    user_id = current_user["sub"] if current_user != None else None

    # Create a new MovieDAO Instance
    dao = MovieDAO(current_app.driver)

    # Retrieve a paginated list of movies
    output = dao.all(sort, order, limit=limit, skip=skip, user_id=user_id)

    # Return as JSON
    return jsonify(output)

Within the route handler, you can see that:

  1. The sort, order, limit and skip values are extracted from the query string. This allows us to apply sorting and pagination to the results.

  2. A new instance of the MovieDAO is created with the Driver instance that you created in Adding the Driver passed to the constructor.

  3. The results are retrieved via the all() method and are returned by the handler as JSON.

The Movie DAO

The magic happens in the MovieDAO, located at api/dao/movies.py. For this route we are concerned with the all() method.

If we take a closer look at the all() method, we can see that it currently returns a hardcoded list of popular movies from another file in the repository.

python
api/dao/movies.py
def all(self, sort, order, limit=6, skip=0, user_id=None):
    # TODO: Get list from movies from Neo4j
    return popular

You will need to replace these TODO comments with working code to complete the challenge.

Implementing Read Transactions

As you learned in the Sessions and Transactions lesson, you will write the code to open a new session and run the query within a Read Transaction. Once the query has run, you add code to close the session. Then, finally you add code to extract and return the results.

Open api/dao/movies.py

1. Create a Unit of Work

First, create a new get_movies to represent the unit of work.

This function will have one mandatory argument passed to it, a Transaction instance that you can use to execute a Cypher statement using the run() method. The function should also have the sort, order, skip, limit and userId parameters passed through as named parameters - these will be referenced in the query by prefixing the name with a dollar sign ($).

python
Define the Unit of Work
# Define the Unit of Work
def get_movies(tx, sort, order, limit, skip, user_id):
    # Code will go here...

You will need to call the run() method on the tx argument, passing a Cypher statement to retrieve a list of paginated movie results along with named arguments sort, order, skip, limit and userId.

python
Define and Execute a Cypher statement
# Define the cypher statement
cypher = """
    MATCH (m:Movie)
    WHERE m.`{0}` IS NOT NULL
    RETURN m {{ .* }} AS movie
    ORDER BY m.`{0}` {1}
    SKIP $skip
    LIMIT $limit
""".format(sort, order)

# Run the statement within the transaction passed as the first argument
result = tx.run(cypher, limit=limit, skip=skip, user_id=user_id)

Extract a list of Movies from the Result

The individual movie values can then be extracted from the result variable using a list comprehension.

python
# Extract a list of Movies from the Result
return [row.value("movie") for row in result]

Consuming Results

Make sure that you extract the results within the unit of work. Once the transaction function ends, any results that have not been consumed will be lost.

2. Open a new Session

To execute the get_movies function within a Read Transaction, first open a new session:

python
Open a new Session
with self.driver.session() as session:

3. Return the Results

Then the output of the get_movies function can then be returned.

python
Return the results from the Unit of Work
with self.driver.session() as session:
    return session.execute_read(get_movies, sort, order, limit, skip, user_id)

Working Solution

Click here to reveal the completed all() method
python
api/dao/movies.py
def all(self, sort, order, limit=6, skip=0, user_id=None):
    # Define the Unit of Work
    def get_movies(tx, sort, order, limit, skip, user_id):
        # Define the cypher statement
        cypher = """
            MATCH (m:Movie)
            WHERE m.`{0}` IS NOT NULL
            RETURN m {{ .* }} AS movie
            ORDER BY m.`{0}` {1}
            SKIP $skip
            LIMIT $limit
        """.format(sort, order)

        # Run the statement within the transaction passed as the first argument
        result = tx.run(cypher, limit=limit, skip=skip, user_id=user_id)

        # Extract a list of Movies from the Result
        return [row.value("movie") for row in result]

    with self.driver.session() as session:
        return session.execute_read(get_movies, sort, order, limit, skip, user_id)

Testing

To test that this functionality has been correctly implemented, run the following code in a new terminal session:

sh
Running the test
pytest -s tests/02_movie_list__test.py

The test file is located at tests/02_movie_list__test.py.

Are you stuck? Click here for help

If you get stuck, you can see a working solution by checking out the 02-movie-lists branch by running:

sh
Check out the 02-movie-lists branch
git checkout 02-movie-lists

You may have to commit or stash your changes before checking out this branch. You can also click here to expand the Support pane.

Verifying the Test

Highest Rated Movie

The final test in the suite logs out the name of the highest rated movie according to the imdbRating property on each movie. Enter the title of the highest rated movie.

  • ✓ Band of Brothers

Hint

You can also find the answer by running the following Cypher statement:

cypher
MATCH (m:Movie)
WHERE m.imdbRating IS NOT NULL
RETURN m.title
ORDER BY m.imdbRating DESC LIMIT 1

Copy the answer without any quotes or whitespace.

Solution

The answer is Band of Brothers.

Lesson Summary

In this Challenge, you used your knowledge of sessions and transactions to retrieve a list of Movie nodes from the database.

In the next Challenge, you will write data to the database.