How to Build a Connector

Connector classes are at the heart of the Parsons project. When we want to add a new service for users to connect to with Parsons, we build a new Connector class for that service.

The documentation contains a complete list of existing connectors. Requests for new connectors are made and discussed in our issue tracker. Before starting to build a new connector, check to see if there’s any discussion about it in the tracker. Ideally, you’ll have a good sense of what you and/or other users want the connector to do before you start trying to build it. Remember, you can always reach out to the community and ask for advice!

When you’re ready to get started, make sure you have Parsons installed and that the tests run successfully.

Getting Started

The first thing you’ll need to do is create a new folder for your connector. This folder should have the same name as the module (file) within the folder, and the same name as the connector class. For example, the airtable connector is in the “airtable” folder, and the hustle connector is in the “hustle” folder.

Inside the folder, create two files. The first should be named and should be empty. The second will have the same name as your folder - this is the file which will have your connector’s code. For example, in the airtable folder this file is called and in the hustle folder it’s called

The directory should look like this:


Next, add the reference to your connector to parsons/ Specifically, open parsons/, scroll to the end of the other imports, and add the following:

from parsons.yourconnectorname.yourconnectorname import yourconnectorname

Also, in parsons/ add ‘yourconnectorname’ to the end of the list __all__.

Once this is done, open the file. At the top of the file, add the following code to enable logging for our connector:

import logging

logger = logging.getLogger(__name__)

You’ll also want to create the Connector class itself:

class YourConnectorName(object):
    Instantiate class.


    def __init__(self, api_key=None):

The text enclosed in triple quotes “”” “”” is called a DocString, and is used to provide information about the class. Typically, it includes the arguments accepted by the __init__ method of the class.

The __init__ method defines how the class is instantiated. For instance, if you want to get an instance of the Connector class by writing connector = YourConnectorName(table_name, api_key) you’d have to add a table_name argument to go with the api_key argument. Your connector’s init statement will probably require a different set of arguments than we’ve written here, but this makes for a good start.

In our Parsons connector classes, the __init__ method should handle authentication. That is, when we initialize our Connector, we should give it credentials so that it can connect to the third-party service. Then we won’t have to worry about authenticating in the other methods. How exactly you authenticate to the service will depend on the service, but it typically involves getting an api_key or access_token, and it almost always involves creating an account on the service.

(Users of your connector class will need to know how to authenticate too! Take notes of where you signed up for an account and how you got the api key, access token, etc so you can include it in the documentation for your connector.)

We like to give users two different options for getting api keys and other authentication to the connector - passing them as arguments to the __init__ method, and storing them as environmental variables. Use the Parsons utility checkenv to allow for either possibility with code that looks like this:

import logging
from parsons.utilities import check_env

logger = logging.getLogger(__name__)

class YourConnectorName(object):
    Instantiate class.


    def __init__(self, api_key=None):
        self.api_key = check_env.check('YOURCONNECTORNAME_API_KEY', api_key)

This code looks in the environmental variables for the api key and, if it doesn’t find it, uses the api_key passed in.

Most connectors make extensive use of existing client/providers. Most likely, your next step will be to instantiate one of those existing clients using the authentication data, and add it to the class. You can see an example of this in the Airtable Connector.


Parsons has a number of patterns that should be used when developing a connector to ensure that connectors look alike, which makes them easier to use and modify. Not all patterns apply to all connectors, but when reviewing pull requests, the maintainers will be looking to see if you adhere to the patterns described in this document.

In the sections below, we will attempt to enumerate the established patterns. We will use the parsons.mailchimp.mailchimp.Mailchimp connector as an example of how to implement the patterns.

Class initialization

Allow configuration of a connector with environment variables as well as arguments passed to the class initializer. Make use of parsons.utilities.check_env.check function to check that the value was provided either as an argument to the initializer, or in the environment.

When calling into a web API, use the `parsons.utilities.APIConnector` class. The APIConnector class has a number of methods for making web requests, and using the APIConnector helps enforce consistency across connectors. The APIConnector is a wrapper around the Python requests library.

Mailchimp example:

from parsons.utilities import check_env
from parsons.utilities.api_connector import APIConnector

class Mailchimp():
    Instantiate Mailchimp Class

            The Mailchimp-provided application key. Not required if
            ``MAILCHIMP_API_KEY`` env variable set.
        Mailchimp Class

    def __init__(self, api_key=None):
        self.api_key = check_env.check('MAILCHIMP_API_KEY', api_key)
        self.domain = re.findall("(?<=-).+$", self.api_key)[0]
        self.uri = f'https://{self.domain}'
        self.client = APIConnector(self.uri, auth=('x', self.api_key))

In the __init__ method above, the Mailchimp class takes one argument: api_key. The argument has a default value of None, which allows for a user to initialize the connector without any arguments (ie Mailchimp(). If no value is passed for api_key as an argument to the __init__ method, then the check_env.check function will attempt to retrieve the value from the MAILCHIMP_API_KEY environment variable. If the value is neither passed in as argument nor in the environment, the check_env.check method will raise a KeyError exception.

In the last line of the code snippet above, the Mailchimp class creates an APIConnector class, providing the root URL for the API (self.uri). The Mailchimp API accepts basic authentication as an authentication mechanism, so the Mailchimp connector is able to pass the api_key to the APIConnector via the auth keyword argument. If the API for your connector does not support basic authentication, you may need to implement your own authentication (e.g. via request headers).

Your connector’s methods

The methods of your connector should generally mirror the endpoints of the API. Every API is different, but the connector should generally look like the API it is connecting to. Methods of your connector should reference the resources the API is using (e.g. “people”, “members”, “events”).

The following lists rules for naming common endpoints:

  • GET - single record - get_<resource> (e.g. get_event, get_person)

  • GET - multiple records - get_<resource>s (e.g. get_members, get_people)

  • POST - single record - create_<resource> (e.g. create_person, create_tag)

  • PUT - single record - update_<resource> (e.g. update_person, update_event)

  • DELETE - single record - delete_<resource> (e.g. delete_member)

A method’s arguments should mirror the parameters of the API endpoint it is calling. Optional parameters should be optional in your method signature (i.e. default to None).

Use Python docstrings to document every public method of your class. The docstrings for your public methods are used to automatically generate documentation for your connector. Having this documentation for every method makes it easier for users to pick up your connector.

Methods returning multiple values should return a Parsons Table. If the list of results is empty, return an empty Parsons Table (not None). Methods returning a single value should just return the value. If the API could not find the value (eg, the ID provided for a resource was not found), return a None value from the method.

Mailchimp example:

class Mailchimp():

    def get_lists(self, fields=None, exclude_fields=None,
                count=None, offset=None, before_date_created=None,
                since_date_created=None, before_campaign_last_sent=None,
                since_campaign_last_sent=None, email=None, sort_field=None,
        Get a table of lists under the account based on query parameters. Note
        that argument descriptions here are sourced from Mailchimp's official
        API documentation.

            fields: list of strings
                A comma-separated list of fields to return. Reference
                parameters of sub-objects with dot notation.
            exclude_fields: list of strings
                A comma-separated list of fields to exclude. Reference
                parameters of sub-objects with dot notation.
            count: int
                The number of records to return. Default value is 10. Maximum
                value is 1000.
            offset: int
                The number of records from a collection to skip. Iterating over
                large collections with this parameter can be slow. Default
                value is 0.
            before_date_created: string
                Restrict response to lists created before the set date. We
                recommend ISO 8601 time format: 2015-10-21T15:41:36+00:00.
            since_date_created: string
                Restrict results to lists created after the set date. We
                recommend ISO 8601 time format: 2015-10-21T15:41:36+00:00.
            before_campaign_last_sent: string
                Restrict results to lists created before the last campaign send
                date. We recommend ISO 8601 time format:
            since_campaign_last_sent: string
                Restrict results to lists created after the last campaign send
                date. We recommend ISO 8601 time format:
            email: string
                Restrict results to lists that include a specific subscriber's
                email address.
            sort_field: string, can only be 'date_created' or None
                Returns files sorted by the specified field.
            sort_dir: string, can only be 'ASC', 'DESC', or None
                Determines the order direction for sorted results.

            Table Class
        params = {'fields': fields,
                'exclude_fields': exclude_fields,
                'count': count,
                'offset': offset,
                'before_date_created': before_date_created,
                'since_date_created': since_date_created,
                'before_campaign_last_sent': before_campaign_last_sent,
                'since_campaign_last_sent': since_campaign_last_sent,
                'email': email,
                'sort_field': sort_field,
                'sort_dir': sort_dir}

        response = self.get_request('lists', params=params)
        tbl = Table(response['lists'])'Found {tbl.num_rows} lists.')
        if tbl.num_rows > 0:
            return tbl
            return Table()

The get_lists method corresponds to the GET /lists endpoint on the Mailchimp API. The method has a number of arguments (all optional), all of which are described in the docstring. The arguments are then mapped to the name of the endpoints’ parameters, and passed to the APIConnector’s get_request method.

The method can return more than one record, so the results of the call to the API are wrapped in a Parsons Table. If there are no results from the call, an empty table is returned.

Sandbox Access

When developing a Parsons connector, it’s helpful to be able to test your changes against a non-production account. We have set up test accounts with some vendors which you can use for testing by following the steps below.

Accessing and Using Credentials

Step 1: Request Access

Request access to the test account (usually in the form of an API key) by emailing Please provide your GitHub username and some context for your request. Why do you need the account credentials? What are you testing? If a community member recommended you request an account from us, you can include their name here. See Connector-Specific Guidance for additional information we may need to give you access to a specific sandbox.

An example request might look something like “Hi, I’m Ana (abc123 on github), I want to work on the ActionNetwork connector but I don’t know how to test it. At the contributor meeting Betty linked me here and said I should ask you.”

Step 2: Save and Use the Credentials

When using your credentials, please store them as environmental variables rather than including them directly in your code. If you use them in your code and accidentally include them as part of a pull request, we will need to generate new credentials. Let’s try to avoid that hassle!

You can set environmental variables with the following commands:


Some environmental variables may need to be explicitly loaded into scripts for use, but most will not. This is because each Parsons connector automatically looks in the environment for specific variables and uses them when initializing the connector. For example, the Zoom connector looks for ZOOM_API_KEY and ZOOM_API_SECRET. Check the documentation for the precise names of the environmental variables it looks for.

In rare cases you may need to load the environmental variables yourself within the script. You can do so with the following code:

import os

General Best Practices

Since the sandbox accounts are shared with multiple people, we ask contributors to observe some guidelines:

  • Use clear naming conventions when creating test data. Either prefix or suffix data with your initials or use another identifier.

  • Only add mock data to the test account, never real data (especially if there are fields for contact information).

  • Try to limit the amount of data you push in/pull out of the account to only the amount that you need.

  • Leave test data that looks like it was created by someone else in the same state that you found it.

  • Delete test data when you finish testing.

  • Be mindful when sending requests to third party platforms. We don’t want to burden them or to have our account suspended and rate-limited. If you accidentally over-requested from the third-party platform and have been suspended or rate-limited in a way that does not expire after a day or less, please reach out to us so we can try to get access again.

Connector-Specific Guidance

The following connectors have sandbox API keys available. Some connectors have specific best practices or additional information to send along when you request the key from us.

ActionNetwork: In order to access the ActionNetwork sandbox account, we’ll need the email address associated with your ActionNetwork account. Please make an ActionNetwork account if you don’t have one already, and include the associated email in your access request to us.

ControlShift: Please limit your testing to pushing and pulling data in and out and do not use the account for sending mass texts. (The sandbox account has an associated phone number, but it is unnecessary for Parsons testing.)

Hustle: No connector-specific guidance.

Mobilize: No connector-specific guidance.

Strive: No connector-specific guidance.

Finishing up

Testing locally

In order to test locally, you will need to install the version of Parsons that you have been working on. To do that, you will need to install in “editable” mode, which allows you to import your local Parsons code instead of the released code.

To install Parsons in “editable” mode, run the following, where <parsons-path> is the path to the root of the Parsons repository on your local machine.

`bash pip install -e <parsons-path> `

Adding automated tests

  • Add a folder test_yourconnectorname in parsons/test for your connector

  • Add a file to the test_yourconnectorname folder

  • Use the code below as a starting point for your tests

  • Add one “Happy Path” test per public method of your connector

  • When possible mock out any external integrations, otherwise mark your test using the unittest.skipIf decorator (for an example, see test/

For a more detailed guide on writing unit tests, see How to Write Tests for Parsons Connectors

from parsons.yourconnector.yourconnector import YourConnector
import unittest
import requests_mock

from parsons.yourconnector.yourconnector import YourConnector
import unittest
import requests_mock

class TestYourConnector(unittest.TestCase):

    def setUp(self):

        # add any setup code here to run before each test

    def tearDown(self):

        # add any teardown code here to run after each test

    def test_get_things(self, m):

        # Test that campaigns are returned correctly.
        m.get(‘’, json=[])
        yc = YourConnector()
        tbl = yc.get_things()

        self.assertEqual(tbl.num_rows, 0)

Adding documentation

  • Add yourconnectorname.rst to the parsons/docs folder.

  • Use the parsons/docs/_template.rst file as a guide for the documentation for your connector.

  • Add a reference to your connector’s doc file to the parsons/docs/index.rst

  • You just need to add the filename without the .rst extension (ie yourconnector)

  • Be sure to add yourconnector in alphabetical order

Final steps

  • Add any new dependencies to the parsons/requirements.txt file

  • Run the entire suite of Parsons unit tests using the pytest -rf test command

  • Run the linter against Parsons using flake8 –max-line-length=100 parsons

  • Double-check that you have committed all of your code changes to your branch, and that you have pushed your branch to your fork

  • Open a pull request against the move-coop/parsons repository