Python and Anaplan: Pt 2—Anaplan Model Registry Case Study

Options

Welcome back to the second article of our Python and Anaplan series! If you're joining us for the first time, please see Python and Anaplan: Pt 1—Setting Up Your Environme... - Anaplan Community for help setting up your environment. 

What's best next—start using your Python environment in a real and useful scenario. With large tenants, organization of development, test, and production environments for multiple models can become a challenging process. Rather than sifting through the model list manually, let's write a script to do the heavy lifting!

Assumptions/Pre-Requisites:

  • This script assumes that your environment has been set up as per my previous article, but it is not explicitly required. If you have your own preferred environment and package manager set up, feel free to use those! 
  • You have a Model Builder/Workspace Administrator role and have knowledge of basic Anaplan modeling concepts.

"Hello, Anaplan!"—Starting with the Anaplan API

Now that we've got our model up and running, it's time to start using the Anaplan API. If you followed the previous article, you should already have your Python virtual environment set up, with the requests module installed. If not, head back to Python and Anaplan: Pt 1—Setting Up Your Environme... - Anaplan Community

The first step with the Anaplan API is always authenticating yourself, to receive an Anaplan Auth Token that we can then use for future calls. There are two types of authentication that the Anaplan API currently supports, being Basic Authentication (using a username and password) and Certificate Authentication. For more detailed information, see the documentation linked here: Authentication Service API · Apiary

Basic Authentication (Username and Password)

For Basic Authentication, we can make a request to the Authentication API "create token" endpoint using our encoded username and password. 

The Create Token endpoint is given here and expects a POST request. When dealing with RESTful APIs, a POST request is used to send data to a server to create or update a resource. In this case, we are sending our authentication details, which are then checked by the Authentication API, which subsequently creates and sends us back the AnaplanAuthToken we need. 

POST https://auth.anaplan.com/token/authenticate

 

 

 

# %% - this defines our Jupyter code cell (more on this below)
import requests # tells our Python interpreter that we will be using functions from the requests module
from base64 import b64encode # tells our Python interpreter that we will be using the b64encode function from the base64 module

# the authentication API takes the Base64 encoded format of a string with the value "email:password". 
# The function b64encode takes a "bytes-like object", hence the b that prepends the 'email:password' string. 
# The function b64encode ouputs a "bytes" object, but since we need it as a string, we can call the function .decode() on the result. 

user_pw = b64encode(b'email:password').decode() 

# To make our first API call, we need to define our headers. We first define a variable called auth_headers, with an empty dict object ({})

auth_headers = {} 

# We can then add a key called 'Authorization' to this dict.
# We set the value of this key to 'Basic <encoded_user_pw>'
auth_headers['Authorization'] = 'Basic ' + user_pw

# We need to define our request URL - in this case as we are trying to authenticate with the Anaplan API, it will be the authenticate URL. 

auth_url = 'https://auth.anaplan.com/token/authenticate'

# Finally, we can make our first API call - the authentication API expects a POST method, so we use the function in our requests module called post. It expects at least the API endpoint URL, which we pass through as auth_url, and then we define the headers value as auth_headers. As we want to be able to interpret the response, we append the .json() to retrieve the response as a JSON. This is stored as a dict object in auth_json. 

auth_json = requests.post(auth_url, headers=auth_headers).json()

# The token value (that we need for later calls) is in a key called 'tokenValue', which in turn is in an object called 'tokenInfo'. 
# We can reference the auth_request 

try:
    auth_token = auth_json['tokenInfo']['tokenValue']
    print(auth_token)
# If this key is not found, it means that the response is not in the expected structure, and we would get a KeyError. To error handle this, we can then print a friendly debug message. 
except KeyError:
    print('No Token Info found - check your credentials?')

 

 

 

Certificate Authentication 

Another option is using a CA-issued certificate to authenticate. Rather than trying to explain how to generate an authentication token using a CA issued certificate, I will defer to an awesome guide that has been previously written, linked here: Generating Authentication Strings for Using CA Certificates with API 2.0. You will need a few packages to follow the linked guide, so make sure you run the following lines in the terminal of VS Code - make sure you have your virtual environment activated first! 

 

 

 

pip install pyOpenSSL
pip install requests
pip install base64

 

 

 

Coffee Break Tip: Interactive Python Notebooks in VSCode

Without going into too much detail, VSCode supports what is known as interactive Python notebooks. For more info, check out this article here: Working with Jupyter code cells in the Python Interactive window (visualstudio.com). For a quick way to get yourself set up, install the Jupyter package, much like how you installed requests in the previous article. 

 

 

 

pip install jupyter

 

 

 

Jupyter code cells are marked with a # %% comment (you'll see this in some of the sample code in these articles). Each code cell can be run independently, in case you needed to change values and did not want to run your entire script again. 

Retrieving Workspace and Model Details

In this section, we'll explore retrieving details about your current tenant workspace, and the models in each specific workspace. We'll be using an Anaplan Authentication Token for all our subsequent calls, so make sure you're able to complete the previous section before continuing on. 

The two specific API endpoints that we'll be using here are:

GET https://api.anaplan.com/2/0/workspaces
GET https://api.anaplan.com/2/0/models

Both endpoints expect a GET request, and also an Authorization header with the AnaplanAuthToken value that we retrieved earlier. 

Retrieving Workspaces

Using our AnaplanAuthToken, we can set up our mandatory headers, and set up our first real call to the Anaplan API - let's get some data out! 

The Workspaces endpoint also supports a request parameter called tenantDetails that will provide some more detail about the available capacity of the workspace, which we'll use here. 

Following the API documentation (linked here: Anaplan Integration API V2 Guide and Reference · Apiary), we know that a correct response will have a status code of 200, and have a specific JSON schema - an example is copied below. 

JSON objects can be parsed as Python dictionary objects, with the JSON key-value pair becoming the dictionary key-value pair. The following schema is made up of three primary keys, "meta", "status" and "workspaces". The key-value pair "workspaces" is what is relevant to us, and we can see that it is a collection of key-value pairs itself, with each object representing a workspace. The below example only has one workspace within the collection. 

 

 

 

{
  "meta": {
    "schema": "https://api.anaplan.com/2/0/objects/workspace",
    "paging": {
      "currentPageSize": 1,
      "totalSize": 1,
      "offset": 0
    }
  },
  "status": {
    "code": 200,
    "message": "Success"
  },
  "workspaces": [
    {
    "id": "8a8b8c8d8e8f8g8i",
    "name": "Financial Planning",
    "active": true,
    "sizeAllowance": 1073741824,
    "currentSize": 873741824
    }
  ]
}

 

 

 

Knowing this, the following code block will do the following: 

  1. Using our previously obtained authentication token, make a request to the workspaces endpoint.
  2. Verify that the response was successful (check the status code)
  3. Parse the JSON response into a Python object
  4. Retrieve our workspaces in a Python object

 

 

 

#%%
# Let's assume that we have stored our authentication token value in a variable called "auth_token" - we first need to configure our response headers. We will be using the requests module to make our calls, and we can pass our headers as a dictionary object. Each key-value pair in this dictionary object is a header and header-value pair. 

# Initialise an empty dictionary object using curly braces {} 
auth_headers = {}
# We can then add a key to this called "Authorization" with the value being the string "AnaplanAuthToken <auth_token>", where <auth_token> is your token value retrieved earlier 
auth_headers['Authorization'] = 'AnaplanAuthToken ' + auth_token

# Using the requests.get() method, we can make a call to our specified endpoint. Note here that we have added a URL parameter called tenantDetails, with the value true, after the ? symbol. 

# We store the Response object returned by the method in a variable called workspaces_rsp
workspaces_rsp = requests.get('https://api.anaplan.com/2/0/workspaces?tenantDetails=true', headers=auth_headers)

# The Response object has an attribute called status_code, which we can check the value of. For simplicity, we'll assume the call is successful, but you may want to check to handle any errors in a Production setting 

#As we are expecting a JSON object, we can parse the content into a dictionary object called workspaces_dict using the .json() method of the Response object. 

workspaces_dict = workspaces_rsp.json()

# Recalling earlier, the expected response schema is a dictionary with keys meta, status and workspaces. The value of the key workspaces is in turn a list of dictionary objects, which each object being an individual workspace in a tenant. 

# We can store this reference this list in a variable called workspaces_list and then iterate through it with a for loop. Lists are iterable, meaning that we can directly reference each individual item

workspaces_list = workspaces_dict['workspaces']

# The end goal here is to generate a String with a value in a CSV format, that will hold our workspace data that we load into Anaplan - we'll call this "workspace_body"

workspace_body = 'Code,Name,sizeAllowance,currentSize' 

# To do this, we'll loop through our workspaces - each item in our workspaces_list is a dictionary object that represents a workspace. 
for wsp in workspaces_list:
   id = wsp['id']
   name = wsp['name']
   sizeAllowance = wsp['sizeAllowance']
   currentSize = wsp['currentSize']
   workspace_body = workspace_body + f'\n{id},{name},{sizeAllowance},{currentSize}'

 

 

 

Retrieving Models

Much the same way, the Models endpoint returns a list of models, either within a specified workspace, or all available models (depending on which endpoint you use). 

 

 

 

 

model_rsp = requests.get('https://api.anaplan.com/2/0/models?modelDetails=true',headers=auth_headers)

model_json = model_resp.json()

model_list = model_json['models']

model_body = 'Code,Name,Workspace,currentSize'
for mdl in model_list:
   mdl_id = mdl['id']
   mdl_wsp = mdl['wsp']
   mdl_name = mdl['name']
   mdl_size = mdl['currentSize']
   model_body = model_body + f'\n{mdl_id},{mdl_name},{mdl_wsp},{mdl_size}'

 

 

 

 

By This Point...

We would now have retrieved all our workspaces and models in our tenant, and stored the result as a CSV formatted String object in two variables, workspace_body, and model_body. The next steps would be to load this data into an Anaplan model, but first, we'll need to have the model configured. 

Setting up Our Anaplan Model

We will configure a basic Anaplan model that will store some details of your workspaces and the models in each of your workspaces. We'll set up our model to have the following structure:

Lists

  • W1 Workspaces
    • Set as a Numbered List, with a text-formatted property called "Workspace Name"
    • Set the Display Name as the property "Workspace Name"
  • W2 Models
    • Parent Hierarchy set as W1 Workspaces
    • Set as a Numbered List, with a text-formatted property called "Model Name"
    • Set the Display Name as the property "Model Name"

Module

  • W1 Workspace Register
    • Dimensions
      • W1 Workspaces 
    • Line items
      • Name (as Text)
        • Formula = 'W1 Workspaces'.Workspace Name
      • Size Allowance (as Number)
      • Current Size (as Number)
  • W2 Model Register
    • Dimensions
      • W2 Models 
    • Line Items
      • Workspace (list formatted as W1 Workspaces) 
        • Formula = PARENT(ITEM('W2 Models'))
      • Name (as Text)
      • Current Size (as Number)

The next guide will go into the creation of the imports, and then executing them through the API. 

Conclusion

By following this guide you should have been able to (all using the Anaplan API)

* Retrieve an authentication token

* Read administrative metadata relating to your available workspaces and models

What's Next?

To continue this demo of the Anaplan API, we'll create our import actions (from within the Anaplan model), retrieve the relevant import action IDs, and execute them using the API. We'll then monitor the task progress and be able to view the result from within the model. 

Later guides will go through on doing the similar activities, but using the Transactional APIs instead - this will allow us to directly read and write data from an Anaplan model, without configuring actions.