Anaplan API 2.0 Python Library - [Updated March 2022]

jesse_wilson
edited December 2022 in Best Practices

After a significant amount of refactoring, I'm very pleased to announce the latest release of my Python package for the Anaplan Bulk API. This package simplifies interacting with the Anaplan Auth and Bulk APIs. It supports Basic and Certificate authentication, certificates can be used directly or within a Java Keystore file. It supports more robust logging and contains more detailed documentation for its use. This package is now available on GitHub and for download using pip.

Note: The package used to handle Java Keystore files appears to be an inactive project and contains a bug that may cause issues during installation. Details for working around these issues can be found in the Github and PyPi pages for the Anaplan API package.

Note: While all of these scripts have been tested and found to be fully functional, due to the vast amount of potential use cases, Anaplan does not explicitly support custom scripts built by our customers. This article is for information only and does not suggest any future product direction. This library is a work in progress and will be updated with new features once they have been tested.

Getting Started

The attached Python library serves as a wrapper for interacting with the Anaplan API. This article will explain how you can use the library to automate many of the requests that are available in our Apiary, which can be found at https://anaplanbulkapi20.docs.apiary.io/#.

This article assumes you have the requests, cryptography, and pyjks modules installed, as well as Python 3.7 or later. Please make sure you are installing these modules with Python 3, and not for an older version of Python. For more information on these modules, please see their respective websites:

Gathering the Necessary Information

In order to use this library, the following information is required:

  • Anaplan model ID
  • Anaplan workspace ID
  • Anaplan action ID
  • CA certificate key-pair (private key and public certificate), or username and password

There are two ways to obtain the model and workspace IDs:

  1. While the model is open, go Help>About: Help Menu.png
  2. Select the workspace and model IDs from the URL: URL.png

Authentication

Every API request is required to supply valid authentication. There are two (2) ways to authenticate:

  1. Certificate Authentication
  2. Basic Authentication

For full details about CA certificates, please refer to our Anapedia article.

Basic authentication uses your Anaplan username and password.

To create a connection with this library, define the authentication type and details, and the Anaplan workspace and model IDs:

Certificate Files:

 

auth = anaplan.generate_authorization(auth_type='Certificate', cert='path/to/cert.pem', private_key='path/to/key.pem')
conn = AnaplanConnection(authorization=auth, workspace_id='<Workspace ID>', model_id='<Model ID>')
auth = anaplan.generate_authorization(auth_type='Basic', email='user@domain.com', password='password')
conn = AnaplanConnection(authorization=auth, workspace_id='<Workspace ID>', model_id='<Model ID>')

 

 

 Java Keystore:

 

 

from anaplan_api.KeystoreManager import KeystoreManager

keys = KeystoreManager(path='path/to/keystore.jks', passphrase='<Keystore password>', alias='<Key Alias>', key_pass='<Private key password>')
auth = anaplan.generate_authorization(auth_type='Certificate', cert=keys.get_cert(), private_key=keys.get_key())
conn = AnaplanConnection(authorization=auth, workspace_id='<Workspace ID>', model_id='<Model ID>')

 

Getting Anaplan Resource Information

You can use this library to fetch the list of resources within the specified Anaplan model: files, imports, exports, actions, processes:

Example:

 

files_list = anaplan.get_list(conn=conn, resource="files")

 

This returns a data object which contains a list of the specified resource. You can simply treat it like a dictionary, passing in the resource name and the object will return the corresponding ID.

 

print(files_list["Users.csv"])

 

Uploads

You can upload a file of any size, in chunks between 1-50mb. This upload can be both local files, or strings of data held in memory. The package will continue reading from the file/buffer in specified increments until all data is uploaded to your Anaplan model.
Flat file: 

 

 

anaplan.file_upload(conn=conn, file_id=files_list["Users.csv"], chunk_size=2, data='/path/to/file.csv')

 

"Streamed" file:

 

with open('/path/to/file.csv', 'r') as current_csv:
    data = current_csv.read()
    anaplan.file_upload(conn=conn, file_id=files_list["Users.csv"], chunk_size=2, data=data)

 

In the above example, both use local files but demonstrate how you can pass the data in as a string or simply provide the path to the local file. Once the upload is complete, you can move on to running your import action(s).

Executing Actions

You can run any Anaplan action with this script and define a number of times to retry the request if there's a problem. In order to execute an Anaplan action, the ID is required. To execute, all that is required is the following:

 

 

results = anaplan.execute_action(conn=conn, action_id="118000000007", retry_count=3)

#For Anaplan Processes
for item in results:
    print(item)

#For other Anaplan actions
print(results)

 

Example output:

Screenshot 2022-02-24 at 16.56.10.png

 

Once the specified action is complete, an object with the overall task results, task details, the file download (if applicable), and error dump (if applicable) is returned. Error dumps are only available for import actions, and are returned in a Pandas DataFrame. If the action is an export, the file is automatically downloaded and available as a string.

Downloading a File

There is no longer a dedicated need to download files, as export actions automatically pull the specified file. However, if you wish to download a file from Anaplan, you may do so.

 

 

file = anaplan.get_file(conn=conn, file_id="<File ID>")

 

 

Get Available Workspaces and Models

API 2.0 introduced a new means of fetching the workspaces and models available to a given user. You can use this library to build a key-value dictionary (as above) for these resources.

 

 

from anaplan_api.User import User
from anaplan_api.Workspace import Workspace
from anaplan_api.Model import Model

current_user = User(conn=conn)
current_user.get_current_user()

workspace = Workspace(conn=conn, user_id=current_user.get_id())
workspaces = workspace.get_workspaces()

print(workspaces)

model = Model(conn=conn, user_id=current_user.get_id())
models = model.get_models()

print(models)

 

 

This example will request details for current user, then fetch the workspaces and models available to that user.

Example Code

Screenshot 2022-02-24 at 16.49.25.png

 

Got feedback on this content? Let us know in the comments below.

Contributing author Anne-Julie Balsamo.

Comments

  • Thanks for the library! For the get_file function, we are struggling when the export contains a comma. The function uses the results of the get .text to parse this out, but it is done by comma separated values. Is there a way to add a text qualifier? Thanks

  • @CommunityMember81209 

     

    Sorry, I can't reproduce this. Whether the comma occurs in a text field or number, the payload of my file is correct (both CSV and TXT). A text delimiter is added to text fields that contain commas, and the commas is dropped from numeric fields (commas as decimal point are converted to period). 

  • Hello, We have found an issue in the get_file function. The URL needs to be inside the while loop that grabs each chunk and the file needs to be appended with each chunk of data. New code: 

    #===========================================================================
    # This function downloads a file from Anaplan to a string.
    #===========================================================================
    def get_file(conn, fileId):
    '''
    :param conn: AnaplanConnection object which contains authorization string, workspace ID, and model ID
    :param fileId: ID of the Anaplan file to download
    '''

    chunk = 0
    details = get_file_details(conn, fileId)
    chunk_count = details[0]
    file_name = details[1]

    authorization = conn.authorization
    workspaceGuid = conn.workspaceGuid
    modelGuid = conn.modelGuid

    get_header = {
    "Authorization": authorization,
    }
    file = ""
    print("Fetching file " + fileId + "...")

    while int(chunk) < int(chunk_count):
    url = __base_url__ + "/" + workspaceGuid + "/models/" + modelGuid + "/files/" + fileId + "/chunks/" + str(chunk)
    try:
    file_contents = requests.get(url, headers=get_header)
    file_contents.raise_for_status()
    except HTTPError as e:
    raise HTTPError(e)
    if file_contents.ok:
    file = file + file_contents.text
    else:
    return "There was a problem fetching the file: " + file_name
    break
    chunk = str(int(chunk) + 1)

    if int(chunk) == int(chunk_count):
    print("File download complete!")

    return file

  • @AdamF  I am also struggling with comma separated values.  I can usually fix it using parsing code in python, but it's not very efficient, and also very troublesome. 

     

    What else do I need to do for file_contents.text to not include commas in the content?

  • Hey, I am actually writing to say I think the project may be broken right now? Getting an error... tried importing from anaplan_api.user import user as well, couldnt get any combination to work!



    Line 1, in <module>
    from anaplan_api import anaplan
    File "/home/anaplant/.local/lib/python3.10/site-packages/anaplan_api/__init__.py", line 25, in <module>
    from .KeystoreManager import KeystoreManager
    File "/home/anaplant/.local/lib/python3.10/site-packages/anaplan_api/KeystoreManager.py", line 9, in <module>
    from anaplan_api import jks
    ImportError: cannot import name 'jks' from partially initialized module 'anaplan_api' (most likely due to a circular import) (/home/anaplant/.local/lib/python3.10/site-packages/anaplan_api/__init__.py)

    I checked your github page and saw that some of the dependencies for JKS and PYJKS were causing issues? Cryptodemex and twofish - I uninstalled those packages but that didn't fix the issue. Here is a pip freeze of what I have in my venv..

    anaplan-api==0.1.28
    apturl==0.5.2
    autopep8==1.6.0
    bcrypt==3.2.0
    blinker==1.4
    Brlapi==0.8.3
    certifi==2020.6.20
    chardet==4.0.0
    click==8.0.3
    colorama==0.4.4
    command-not-found==0.3
    cryptography==3.4.8
    cupshelpers==1.0
    dbus-python==1.2.18
    defer==1.0.6
    distro==1.7.0
    distro-info===1.1build1
    duplicity==0.8.21
    fasteners==0.14.1
    future==0.18.2
    httplib2==0.20.2
    idna==3.3
    importlib-metadata==4.6.4
    javaobj-py3==0.4.3
    jeepney==0.7.1
    keyring==23.5.0
    language-selector==0.1
    launchpadlib==1.10.16
    lazr.restfulclient==0.14.4
    lazr.uri==1.0.6
    lockfile==0.12.2
    louis==3.20.0
    macaroonbakery==1.3.1
    Mako==1.1.3
    MarkupSafe==2.0.1
    monotonic==1.6
    more-itertools==8.10.0
    netifaces==0.11.0
    numpy==1.23.0
    oauthlib==3.2.0
    olefile==0.46
    pandas==1.4.3
    paramiko==2.9.3
    pexpect==4.8.0
    Pillow==9.0.1
    protobuf==3.12.4
    ptyprocess==0.7.0
    pyasn1==0.4.8
    pyasn1-modules==0.2.8
    pycairo==1.20.1
    pycodestyle==2.8.0
    pycups==2.0.1
    PyGObject==3.42.0
    pyjks==20.0.0
    PyJWT==2.3.0
    pymacaroons==0.13.0
    PyNaCl==1.5.0
    pyparsing==2.4.7
    pyRFC3339==1.1
    python-apt==2.3.0+ubuntu2
    python-dateutil==2.8.1
    python-debian===0.1.43ubuntu1
    pytz==2022.1
    pyxdg==0.27
    PyYAML==5.4.1
    reportlab==3.6.8
    requests==2.25.1
    SecretStorage==3.3.1
    six==1.16.0
    systemd-python==234
    toml==0.10.2
    ubuntu-advantage-tools==27.8
    ubuntu-drivers-common==0.0.0
    ufw==0.36.1
    unattended-upgrades==0.1
    urllib3==1.26.5
    usb-creator==0.3.7
    wadllib==1.3.6
    xdg==5
    xkit==0.0.0
    zipp==1.0.0


    not sure if you have a text file with the packages needed? Appreciate any help you can give! @jesse_wilson  let me know if you are able to reproduce this as well... i did a fresh pip install of all the packages as of yesterday...


  • Leaving a note here in case in helps others. As Jesse mentioned in his article as well as in the GitHub repo notes, there is an issue with installing PyJKS.

    The instructions for working around the issue on the GitHub page indicate:

    You can simply download, remove the unnecessary files, and drop the jks folder in your site-package directory to work around the error.

    Here are some more concrete steps to follow this workaround:

    1. Navigate to the PyPi or GitHub page for the PyJKS project.

    2. Download the source code via either source.

    PyPi:

    GitHub:

    3. Locate the jks folder within the PyJKS code.

    4. Navigate to your site-packages folder.

    5. Navigate to the anaplan_api folder within site-packages.

    6. Copy the jks folder from PyJKS into the anaplan_api folder.

    These steps resolved the issue for me.

  • sergio.del-villar
    edited May 7

    Hi @jesse , I have seen that you're back in action :)
    I'm trying the new API but I run into this


    #trying new

    import os

    import anaplan

    import anaplan.anaplan

    TxtUserDownloadsFolder=os.path.join(os.path.expanduser("~"), "Downloads")

    TxtUserDownloadsFolder=TxtUserDownloadsFolder.replace("\\", "/") +"/"TxtCertPath=TxtUserDownloadsFolder + 'mycert.pem';TxtPrivateKeyPath=TxtUserDownloadsFolder + 'mykey.key'

    auth=anaplan.anaplan.authorize("certificate",private_key=TxtPrivateKeyPath,certificate=TxtCertPath)anaplan.anaplan.AnaplanConnection(auth,"82XXX","1243XXX")

    Any updated instructions or related? It's worth to note that my key is passprotected

  • @sergio.del-villar Thanks for raising this bug, I just pushed a patch that should fix it in the latest version: 0.2.10.

    If you run into any other bugs, feel free to open an issue here: https://github.com/jeswils-ap/anaplan-api