Toggling classes archival status via the API

The "Bulk update or create a Class" endpoint is a unique endpoint with specific behaviors that may need further understanding. This article is intended to give some guidance for data managers or integration specialists on how to use it effectively. This article will also demonstrate how to use this and the "Get all Classes" endpoint in order to build a complete solution.

This tutorial will use Python, the popular library Requests, and built-in data structures to demonstrate how to accomplish this goal.

Use Case

We want to select classes that have "Study Hall" in the name, and toggle its `archived` status. We will use this feature when running ManageBac+ reports, for ease in hiding them from the report builder. In this way, we have a mechanism that excludes classes from being reported on, in a way that directly addresses our specific requirements.

Steps

  1. The program will fetch all of the currently active classes from the "Get all Classes" endpoint

  2. It will loop over every class and identify whether this is a class whose status we would like to toggle

  3. It will send patch request to the "Bulk update or create a Class" endpoint with the body build in step 2

  4. Check the responses, and if necessary, reports errors

Code

Below is demonstrated the core part of the program that defines the variables and library, the main function, which simply calls some other functions. The variable "classes_url" and "set_archived_status_to" will be used later:

import requests

# change this to whether you want Study Hall classes to be archived (True) or not (False)
set_archived_status_to = True

top_level_domain = 'com'
classes_url = f'https://api.managebac.{top_level_domain}/v2/classes'

def main():
    study_halls = fetch_active_study_hall_classes()
    errors = set_status(study_halls)
    process_errors(study_halls, errors)
    
# .. definitelye function here
main()

As we can see, the main function fetches active study hall classes, and passes the response to the set_status function. We get back the errors, which we process by calling process_errors with both the study halls and the errors.

Fetch study halls

The function fetch_active_study_hall_classes will use the API endpoint "Get all Classes" to find all classes.

def fetch_active_study_hall_classes() -> list:
    page = 1
    classes = []
    
    while True:
        # api request here: params are the query parameters on this endpoint
        response = requests.get(classes_url, params={'page': page, 'archived': not set_archived_status_to})
        response.raise_for_status()
        
        # parse the body
        body_json: dict = response.json()
        body_meta: dict = body_json.get('meta', {})
        body_classes: list = body_json.get('classes', [])

The classes endpoint takes a query parameter "archived" that needs to be passed as "true" or "1" if the classes you are fetching are archived. By default, it responds with active classes. This line will build the GET request required, depending on the value of set_archived_status_to:

response = requests.get(classes_url, params={'page': page, 'archived': not set_archived_status_to})

That will build the following request:

GET https://api.managebac.com/v2/classes?page=1&archived=x

Where "x" will be the opposite of the value we will set the archived status to. In that way, if we want to set Study Hall classes to archived, it will fetch active classes.

Now that we have got the response from the endpoint, we loop through the classes in the body. We will only keep the classes properties that are required to update: The class ID and "archived" set to the intended value:

# loop through the classes key provide in the response
for class_ in body_classes:
    id_ = class_.get('id')
    name = class_.get('name')
    
    if 'Study Hall' in name:
        # add dictionary with two elements that is needed for bulk update
        classes.append(
            {
                'id': id_,
                'archived': set_archived_status_to
            }
       )
       
   page = body_meta.get('next_page')
   if not page:
       break
       
return classes

We now have a list of records, where each record has an ID and archived property. It will look like this:

[
  {
    "id": 1234567,
    "archived": True
  }, {
    "id": 93223,
    "archived": True
  }, {
  # etc
  }
]

We will now reach out to the ManageBac+ endpoint that can perform the bulk update. According to the documentation, the payload body needs to have a "classes" property with the list above:

{
  "classes": [
    {
      "id": 1234567,
      "archived": True
    }, {
      "id": 93223,
      "archived": True
    }, {
      # etc
    }
  ]
}

We will now start to write the function that sends this data:

The "Bulk Update or Create" endpoint requires us to use the PATCH method at the same endpoint URL used previously. Since it is a different method, instead of returning with classes, it takes action on ManageBac+ and then returns individual responses for each action. We store the individual responses in the variable "all_responses".

def set_statu(classes) -> list:
    """ Use the "bulk Update or Create" endpoint and return any indivdiaul responses """
    
    payload = {
        'classes': classes
    }
    
    response = requests.patch(classes_url, json=payload)
    response.raise_for_status()
    
    all_responses = response.json()

Let's loop over the responses, and see if we can find any errors, and return them:

    errors = []
    for individual_response in all_responses:
        status = individual_response.get('status')
        if not status == 'ok':
            errors.append(individual_response)
    return errors

Finally, we implement the "process_errors" function:

def process_errors(classes: list, errors: list) -> None:
    for error in errors:
        index: int = error.get('index')
        class_that_errored = classes[index]
        print(class_that_erroed)

Source Code

Please find the source code used in this article:

"""
1. The program will fetch all of the currently active classes from the "Get all Classes" endpoint
2. It will loop over every class and identify whether this is a class whose status we would like to toggle
3. It will send patch request to the "Bulk update or create a Class" endpoint with the body build in step 2 
4. Check the responses, and if necessary, reports errors
"""
import requests

set_archived_status_to = True  # change this to whether you want Study Hall classes to be archived (True) or not (False)

top_level_domain = 'com'  # change to cn or eu or us as appropriate
classes_url = f'https://api.managebac.{top_level_domain}/v2/classes'


def fetch_active_study_hall_classes() - list:
    """ Fetch active classes with 'Study Hall' in the name"""

    page = 1
    classes = []

    while True:
        # api request here: params are the query parameters on this endpoint
        response = requests.get(classes_url, params={'page': page, 'archived': not set_archived_status_to})
        response.raise_for_status()

        # parse the body
        body_json: dict = response.json()
        body_meta: dict = body_json.get('meta', {})
        body_classes: list = body_json.get('classes', [])

        # loop through the classes key provided in the response
        for class_ in body_classes:
            id_ = class_.get('id')
            name = class_.get('name')

            if 'Study Hall' in name:
                # add dictionary with two elements that is needed for bulk update
                classes.append(
                    {
                        'id': id_,
                        'archived': set_archived_status_to
                    }
                )

        page = body_meta.get('next_page')
        if not page:
            # no more pages
            break

    return classes


def set_status(classes) - list:
    """ Use the "Bulk Update or Create" endpoint and return any individual responses that are errors """

    payload = {
        'classes': classes
    }

    response = requests.patch(classes_url, json=payload)
    response.raise_for_status()

    all_responses = response.json()

    errors = []
    for individual_response in all_responses:
        status = individual_response.get('status')
        if not status == 'ok':
            errors.append(individual_response)
    return errors


def process_errors(classes: list, errors: list) - None:
    for error in errors:
        index: int = error.get('index')
        class_that_errored = classes[index]
        print(class_that_errored)


def main():
    study_halls = fetch_active_study_hall_classes()
    errors = set_status(study_halls)
    process_errors(study_halls, errors)


# ... define functions here


main()

Last updated

Was this helpful?