CSCI 338: Spring 2025

Software Engineering

CSCI 338: Spring 2025

Assignments > Lab 7: AsyncIO + MVC

Due on Wed, 03/26 @ 11:59PM. 6 Points.

Set Up

Before you begin, get the latest code from class-exercises-spring2025.

On GitHub:

On your local computer:

Install the dependencies, run your webserver

Once you’ve created your lab07-b branch, open the lab07 folder in your code editor, and navigate to the lab07 folder on your terminal. We will not be using Docker – just poetry. Please install the dependencies and run your app as follows:

poetry install
cd app
poetry run uvicorn server:app --reload

Note that if port 8000 is already being used, you can always run your web server on a different port by using the --port flag:

poetry run uvicorn server:app --reload --host 0.0.0.0 --port 8001

If you did this correctly you should see some output like this:

No dependencies to install or update
INFO:     Will watch for changes in these directories: ['/../csci338/spring2025/class-exercises-spring2025/lab07/app']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [27120] using StatReload
INFO:     Started server process [27124]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

You are now ready to use your app.

Test out your app

Questions to ask yourself

  • Where is this data coming from?
  • Is there a way to make this interface more user-friendly and engaging?
  • How is AsyncIO being used here, and what advantages are there to using it?

Introduction to MVC

Lab 7 implements a MVC architecture (Model, View, Controller) architecture using a few different Python libararies including:

  1. FastAPI,
  2. AsyncIO, and
  3. Jinja (a templating engine)

MVC is a design pattern that separates an application into three interconnected components:

This separation makes the code easier to manage and scale. Let’s walk through the code to understand how these components work together.

Models - Organizing and Fetching Data

Start by opening the models.py. This file handles data fetching. Each function retrieves information from various APIs, by using a helper function that makes asynchronous network requests:

# async helper function:
async def fetch_data_from_server(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            try:
                return await response.json()
            except:
                response = await response.text()
                return {
                    "message": (
                        f"Error decoding JSON data. Here was the response: `{response}`"
                    )
                }

Let’s break this function down, because it’s at the heart of this app:

1. Asynchronous function definition

async def fetch_data_from_server(url):

An asynchronous function definition (declared with async def) can perform non-blocking I/O operations, like making HTTP requests without blocking the rest of the program.

2. Aiohttp Client Session

async with aiohttp.ClientSession() as session:

3. Sending a GET Request:

async with session.get(url) as response:

4. Handling the Response:

return await response.json()

5. Error Handling:

except:
    response = await response.text()
    return {
        "message": (
            f"Error decoding JSON data. Here was the response: `{response}`"
        )
    }

Network requests often fail for a variety of reasons. If JSON decoding fails (meaning that there’s a problem with the message), the except block handles the error.

Example Model:

Let’s take a look at the get_yelp_data function…

async def get_yelp_data(place):
    url = f"{ENDPOINT_YELP}?location={place}&limit=20"
    print(url)
    places = await fetch_data_from_server(url)
    try:
        return random.choice(places)
    except:
        return places

Using the fetch_data_from_server helper function, this function’s job is to first query for the first 20 businesses associated with the place that is passed in as an argument, and then it returns one of those businesses at random.

1. Model Task

  1. Add a new function get_dog_image() that retrieves a random dog image from the “https://dog.ceo/api/breeds/image/random” API.

Controller - Managing Requests

The server.py file acts as the controller. It receives HTTP requests, fetches data using model functions, and returns a response.

Example:

@app.get("/")
async def home(request: Request, place_name: str = Query(None)):
    if place_name is None:
        return "The `place_name` query parameter is required."

    start = time.time()
    # we're making these I/O-bound function invocations asynchronous:
    weather_data, yelp_data, wikipedia_data, cat_fact, joke = await asyncio.gather(
        models.get_weather(place_name),
        models.get_yelp_data(place_name),
        models.get_wikipedia(place_name),
        models.get_cat_fact(),
        models.get_joke(),
    )
    end = time.time()

    return templates.TemplateResponse(
        "index.html",
        {
            "request": request,
            "time_taken": f"{end - start:.2f} seconds",
            "place_name": place_name,
            "weather": weather_data,
            "cat_fact": cat_fact["fact"],
            "joke": joke,
            "yelp_data": yelp_data,
            "wikipedia_data": wikipedia_data,
        },
    )

This function listens for GET requests on the / route. The user is required to provide a place_name argument. The function then invokes multiple model functions asynchronously using the await asyncio.gather() function. Once all coroutines resolve (after responses are received from the various servers), the data are passed into a templating function (rendered in the view)

2. Controller Task

  1. Modify both the / and the /api endpoints so that data from the get_dog_image() function is output (name the key dog_data).
  2. Visit http://localhost:8000/api to verify the result.

View - Displaying Data

The index.html file inside the templates/ directory presents data to the user. This template is a combination of “vanilla” HTML and python expressions (surrounded by double curly braces. Note that only keys that were passed into the template are available to the template.

Example:

 <p>Cat Fact: </p>

3. View Task

  1. Add a new <img src="??????" /> element to display a photo of your dog (stored in the dog_data key) in index.html. Use the double curly braces to output the correct value from the data.

Extra Tasks

Useful documentation

What to Submit

Verify that you have completed the three tasks described above:

  1. Model Task
  2. Controller Task
  3. View Task

When you’re done, commit and push your lab07-b branch to GitHub and make a pull request. Please ensure that the destination (left-hand side) is pointing to the main branch of your repo and the source (right-hand side) is pointing to the lab06 branch of your repo. Then, please paste a link to your PR in the Moodle.