diff --git a/docs/bluelink.rst b/docs/bluelink.rst
deleted file mode 100644
index 985540a62..000000000
--- a/docs/bluelink.rst
+++ /dev/null
@@ -1,69 +0,0 @@
-Bluelink
-=============
-
-********
-Overview
-********
-
-`Bluelink `_ is an online tool for connecting various `digital software tools `_ used by campaigns and movement groups in the political and non-profit space so you can sync data between them. This integration currently supports sending your structured person data and related tags to Bluelink via the `Bluelink Webhook API `_, after which you can use Bluelink's UI to send to any of their `supported tools `_. If you don't see a tool you would like to connect to, please reach out at hello@bluelink.org to ask them to add it.
-
-.. note::
- Authentication
- If you don't have a Bluelink account please complete the `form `_ on the Bluelink website or email them at hello@bluelink.org. To get connection credentials select `Bluelink Webhook `_ from the apps menu. If you don't see this option, you may need to ask an account administrator to do this step for you.
-
- The credentials are automatically embedded into a one time secret link in case they need to be sent to you. Open the link to access the user and password.
-
-==========
-Quickstart
-==========
-
-To instantiate a class, you can either pass in the user and password token as arguments or set them in the
-BLUELINK_WEBHOOK_USER and BLUELINK_WEBHOOK_PASSWORD environment variables.
-
-.. code-block:: python
-
- from parsons.bluelink import Bluelink
-
- # First approach: Use API credentials via environmental variables
- bluelink = Bluelink()
-
- # Second approach: Pass API credentials as arguments
- bluelink = Bluelink('username', 'password')
-
-You can upsert person data by directly using a BluelinkPerson object:
-
-.. code-block:: python
-
- from parsons.bluelink import Bluelink, BluelinkPerson, BluelinkIdentifier
-
- # create the person object
- person = BluelinkPerson(identifiers=[BluelinkIdentifier(source="SOURCE_VENDOR", identifier="ID")], given_name="Jane", family_name="Doe")
-
- # use the bluelink connector to upsert
- source = "MY_ORG_NAME"
- bluelink.upsert_person(source, person)
-
-You can bulk upsert person data via a Parsons Table by providing a function that takes a row and outputs a BluelinkPerson:
-
-.. code-block:: python
-
- from parsons.bluelink import Bluelink, BluelinkPerson, BluelinkIdentifier
-
- # a function that takes a row and returns a BluelinkPerson
- def row_to_person(row):
- return BluelinkPerson(identifiers=[BluelinkIdentifier(source="SOURCE_VENDOR", identifier=row["id"])],
- given_name=row["firstName"], family_name=row["lastName"])
-
- # a parsons table filled with person data
- parsons_tbl = get_data()
-
- # call bulk_upsert_person
- source = "MY_ORG_NAME"
- bluelink.bulk_upsert_person(source, parsons_tbl, row_to_person)
-
-***
-API
-***
-
-.. autoclass :: parsons.bluelink.Bluelink
- :inherited-members:
diff --git a/docs/index.rst b/docs/index.rst
index b3cba40fc..bb057fbca 100755
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -189,7 +189,6 @@ Indices and tables
azure
bill_com
bloomerang
- bluelink
box
braintree
capitolcanary
diff --git a/parsons/__init__.py b/parsons/__init__.py
index 6762e54e2..ebc743e37 100644
--- a/parsons/__init__.py
+++ b/parsons/__init__.py
@@ -39,7 +39,6 @@
("parsons.azure.azure_blob_storage", "AzureBlobStorage"),
("parsons.bill_com.bill_com", "BillCom"),
("parsons.bloomerang.bloomerang", "Bloomerang"),
- ("parsons.bluelink", "Bluelink"),
("parsons.box.box", "Box"),
("parsons.braintree.braintree", "Braintree"),
("parsons.capitol_canary.capitol_canary", "CapitolCanary"),
diff --git a/parsons/bluelink/__init__.py b/parsons/bluelink/__init__.py
deleted file mode 100644
index d3c77bf6f..000000000
--- a/parsons/bluelink/__init__.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from parsons.bluelink.bluelink import Bluelink
-from parsons.bluelink.person import (
- BluelinkPerson,
- BluelinkEmail,
- BluelinkAddress,
- BluelinkPhone,
- BluelinkIdentifier,
- BluelinkTag,
- BluelinkScore,
-)
-
-__all__ = [
- "Bluelink",
- "BluelinkPerson",
- "BluelinkEmail",
- "BluelinkAddress",
- "BluelinkPhone",
- "BluelinkIdentifier",
- "BluelinkTag",
- "BluelinkScore",
-]
diff --git a/parsons/bluelink/bluelink.py b/parsons/bluelink/bluelink.py
deleted file mode 100644
index 93e2f933d..000000000
--- a/parsons/bluelink/bluelink.py
+++ /dev/null
@@ -1,84 +0,0 @@
-from parsons.utilities.api_connector import APIConnector
-from parsons.utilities import check_env
-from parsons.bluelink.person import BluelinkPerson
-import logging
-import json
-
-logger = logging.getLogger(__name__)
-
-API_URL = "https://api.bluelink.org/webhooks/"
-
-
-class Bluelink:
- """
- Instantiate a Bluelink connector.
- Allows for a simple method of inserting person data to Bluelink via a webhook.
- # see: https://bluelinkdata.github.io/docs/BluelinkApiGuide#webhook
-
- `Args:`:
- user: str
- Bluelink webhook user name.
- password: str
- Bluelink webhook password.
- """
-
- def __init__(self, user=None, password=None):
- self.user = check_env.check("BLUELINK_WEBHOOK_USER", user)
- self.password = check_env.check("BLUELINK_WEBHOOK_PASSWORD", password)
- self.headers = {
- "Content-Type": "application/json",
- }
- self.api_url = API_URL
- self.api = APIConnector(self.api_url, auth=(self.user, self.password), headers=self.headers)
-
- def upsert_person(self, source, person=None):
- """
- Upsert a BluelinkPerson object into Bluelink.
- Rows will update, as opposed to being inserted, if an existing person record in
- Bluelink has a matching BluelinkIdentifier (same source and id) as the BluelinkPerson object
- passed into this function.
-
- `Args:`
- source: str
- String to identify that the data came from your system. For example,
- your company name.
- person: BluelinkPerson
- A BluelinkPerson object.
- Will be inserted to Bluelink, or updated if a matching record is found.
- `Returns:`
- int
- An http status code from the http post request to the Bluelink webhook.
- """
- data = {"source": source, "person": person}
- jdata = json.dumps(
- data,
- default=lambda o: {k: v for k, v in o.__dict__.items() if v is not None},
- )
- resp = self.api.post_request(url=self.api_url, data=jdata)
- return resp
-
- def bulk_upsert_person(self, source, tbl, row_to_person):
- """
- Upsert all rows into Bluelink, using the row_to_person function to
- transform rows to BluelinkPerson objects.
-
- `Args:`
- source: str
- String to identify that the data came from your system.
- For example, your company name.
- tbl: Table
- A parsons Table that represents people data.
- row_to_person: Callable[[dict],BluelinkPerson]
- A function that takes a dict representation of a row from the passed in tbl
- and returns a BluelinkPerson object.
-
- `Returns:`
- list[int]
- A list of https response status codes, one response for each row in the table.
- """
- people = BluelinkPerson.from_table(tbl, row_to_person)
- responses = []
- for person in people:
- response = self.upsert_person(source, person)
- responses.append(response)
- return responses
diff --git a/parsons/bluelink/person.py b/parsons/bluelink/person.py
deleted file mode 100644
index f808c090f..000000000
--- a/parsons/bluelink/person.py
+++ /dev/null
@@ -1,280 +0,0 @@
-import logging
-import json
-
-logger = logging.getLogger(__name__)
-
-
-class BluelinkPerson(object):
- """
- Instantiate BluelinkPerson Class.
- Used for to upserting via Bluelink connector.
- See: https://bluelinkdata.github.io/docs/BluelinkApiGuide#person-object
-
- `Args:`
- identifiers: list[BluelinkIdentifier]
- A list of BluelinkIdentifier objects.
- A BluelinkPerson must have at least 1 identifier.
- given_name: str
- First name / given name.
- family_name: str
- Last name / family name.
- phones: list[BluelinkPhone]
- A list of BluelinkPhone objects representing phone numbers.
- emails: list[BluelinkEmail]
- A list of BluelinkEmail objects representing email addresses.
- addresses: list[BluelinkAddress]
- A list of BluelinkAddress objects representing postal addresses.
- tags: list[BluelinkTag]
- Simple tags that apply to the person, eg DONOR.
- employer: str
- Name of the persons employer.
- employer_address: BluelinkAddress
- BluelinkAddress of the persons employer.
- occupation: str
- Occupation.
- scores: list[BluelinkScore]
- List of BluelinkScore objects. Scores are numeric scores, ie partisanship model.
- birthdate: str
- ISO 8601 formatted birth date: 'YYYY-MM-DD'
- details: dict
- additional custom data. must be json serializable.
- """
-
- def __init__(
- self,
- identifiers,
- given_name=None,
- family_name=None,
- phones=None,
- emails=None,
- addresses=None,
- tags=None,
- employer=None,
- employer_address=None,
- occupation=None,
- scores=None,
- birthdate=None,
- details=None,
- ):
-
- if not identifiers:
- raise Exception(
- "BluelinkPerson requires list of BluelinkIdentifiers with "
- "at least 1 BluelinkIdentifier"
- )
-
- self.identifiers = identifiers
- self.addresses = addresses
- self.emails = emails
- self.phones = phones
- self.tags = tags
- self.scores = scores
-
- self.given_name = given_name
- self.family_name = family_name
-
- self.employer = employer
- self.employer_address = employer_address
- self.occupation = occupation
- self.birthdate = birthdate
- self.details = details
-
- def __json__(self):
- """The json str representation of this BluelinkPerson object"""
- return json.dumps(self, default=lambda obj: obj.__dict__)
-
- def __eq__(self, other):
- """A quick and dirty equality check"""
- dself = json.loads(self.__json__())
- dother = json.loads(other.__json__())
- return dself == dother
-
- def __repr__(self):
- return self.__json__()
-
- @staticmethod
- def from_table(tbl, dict_to_person):
- """
- Return a list of BluelinkPerson objects from a Parsons Table.
-
- `Args:`
- tbl: Table
- A parsons Table.
- dict_to_person: Callable[[dict],BluelinkPerson]
- A function that takes a dictionary representation of a table row,
- and returns a BluelinkPerson.
- `Returns:`
- list[BluelinkPerson]
- A list of BluelinkPerson objects.
- """
- return [dict_to_person(row) for row in tbl]
-
-
-class BluelinkIdentifier(object):
- """
- Instantiate an BluelinkIdentifier object.
- BluelinkIdentifier is necessary for updating BluelinkPerson records.
-
- `Args:`
- source: str
- External system to which this ID belongs, e.g., “VAN:myCampaign”.
- Bluelink has standardized strings for source. Using these will
- allow Bluelink to correctly understand the external IDs you add.
- source (unlike identifier) is case insensitive.
- examples: BLUELINK, PDI, SALESFORCE, VAN:myCampaign, VAN:myVoters
- identifier: str
- Case-sensitive ID in the external system.
- details: dict
- dictionary of custom fields. must be serializable to json.
- """
-
- def __init__(self, source, identifier, details=None):
- self.source = source
- self.identifier = identifier
- self.details = details
-
-
-class BluelinkEmail(object):
- """
- Instantiate an BluelinkEmail object.
-
- `Args:`
- address: str
- An email address. ie "user@example.com"
- primary: bool
- True if this is known to be the primary email.
- type: str
- Type, eg: "personal", "work"
- status: str
- One of "Potential", "Subscribed", "Unsubscribed", "Bouncing", or "Spam Complaints"
- """
-
- def __init__(self, address, primary=None, type=None, status=None):
- self.address = address
- self.primary = primary
- self.type = type
- self.status = status
-
-
-class BluelinkAddress(object):
- """
- Instantiate an BluelinkAddress object.
-
- `Args`:
- address_lines: list[str]
- A list of street address lines.
- city: str
- City or other locality.
- state: str
- State in ISO 3166-2.
- postal_code: str
- Zip or other postal code.
- country: str
- ISO 3166-1 Alpha-2 country code.
- type: str
- The type. ie: "home", "mailing".
- venue: str
- The venue name, if relevant.
- status: str
- A value representing the status of the address. "Potential", "Verified" or "Bad"
- """
-
- def __init__(
- self,
- address_lines=None,
- city=None,
- state=None,
- postal_code=None,
- country=None,
- type=None,
- venue=None,
- status=None,
- ):
-
- self.address_lines = address_lines or []
- self.city = city
- self.state = state
- self.postal_code = postal_code
- self.country = country
-
- self.type = type
- self.venue = venue
- self.status = status
-
-
-class BluelinkPhone(object):
- """
- Instantiate a BluelinkPhone object.
-
- `Args:`
- number: str
- A phone number. May or may not include country code.
- primary: bool
- True if this is known to be the primary phone.
- description: str
- Free for description.
- type: str
- Type, eg: "Home", "Work", "Mobile"
- country: str
- ISO 3166-1 Alpha-2 country code.
- sms_capable: bool
- True if this number can accept SMS.
- do_not_call: bool
- True if this number is on the US FCC Do Not Call Registry.
- details: dict
- Additional data dictionary. Must be json serializable.
- """
-
- def __init__(
- self,
- number,
- primary=None,
- description=None,
- type=None,
- country=None,
- sms_capable=None,
- do_not_call=None,
- details=None,
- ):
- self.number = number
- self.primary = primary
- self.description = description
- self.type = type
- self.country = country
- self.sms_capable = sms_capable
- self.do_not_call = do_not_call
- self.details = details
-
-
-class BluelinkTag(object):
- """
- Instantiate a BluelinkTag object.
-
- `Args:`
- tag: str
- A tag string; convention is either a simple string
- or a string with a prefix separated by a colon, e.g., “DONOR:GRASSROOTS”
- """
-
- def __init__(self, tag):
- self.tag = tag
-
-
-class BluelinkScore(object):
- """
- Instantiate a score object.
- Represents some kind of numeric score.
-
- `Args`:
- score: float
- Numeric score.
- score_type: str
- Type, eg: "Partisanship model".
- source: str
- Original source of this score.
- """
-
- def __init__(self, score, score_type, source):
- self.score = score
- self.score_type = score_type
- self.source = source
diff --git a/test/test_bluelink/test_bluelink.py b/test/test_bluelink/test_bluelink.py
deleted file mode 100644
index 5c15ea5f7..000000000
--- a/test/test_bluelink/test_bluelink.py
+++ /dev/null
@@ -1,117 +0,0 @@
-import unittest
-import requests_mock
-from parsons import Table, Bluelink
-from parsons.bluelink import BluelinkPerson, BluelinkIdentifier, BluelinkEmail
-
-
-class TestBluelink(unittest.TestCase):
- @requests_mock.Mocker()
- def setUp(self, m):
- self.bluelink = Bluelink("fake_user", "fake_password")
-
- @staticmethod
- def row_to_person(row):
- """
- dict -> BluelinkPerson
- Transforms a parsons Table row to a BluelinkPerson.
- This function is passed into bulk_upsert_person along with a Table
- """
- email = row["email"]
- return BluelinkPerson(
- identifiers=[
- BluelinkIdentifier(source="FAKESOURCE", identifier=email),
- ],
- emails=[BluelinkEmail(address=email, primary=True)],
- family_name=row["family_name"],
- given_name=row["given_name"],
- )
-
- @staticmethod
- def get_table():
- return Table(
- [
- {
- "given_name": "Bart",
- "family_name": "Simpson",
- "email": "bart@springfield.net",
- },
- {
- "given_name": "Homer",
- "family_name": "Simpson",
- "email": "homer@springfield.net",
- },
- ]
- )
-
- @requests_mock.Mocker()
- def test_bulk_upsert_person(self, m):
- """
- This function demonstrates how to use a "row_to_person" function to bulk
- insert people using a Table as the data source
- """
- # Mock POST requests to api
- m.post(self.bluelink.api_url)
-
- # get data as a parsons Table
- tbl = self.get_table()
-
- # String to identify that the data came from your system. For example, your company name.
- source = "BLUELINK-PARSONS-TEST"
-
- # call bulk_upsert_person
- # passing in the source, the Table, and the function that maps a Table row -> BluelinkPerson
- self.bluelink.bulk_upsert_person(source, tbl, self.row_to_person)
-
- @requests_mock.Mocker()
- def test_upsert_person(self, m):
- """
- This function demonstrates how to insert a single BluelinkPerson record
- """
- # Mock POST requests to api
- m.post(self.bluelink.api_url)
-
- # create a BluelinkPerson object
- # The BluelinkIdentifier is pretending that the user can be
- # identified in SALESFORCE with FAKE_ID as her id
- person = BluelinkPerson(
- identifiers=[BluelinkIdentifier(source="SALESFORCE", identifier="FAKE_ID")],
- given_name="Jane",
- family_name="Doe",
- emails=[BluelinkEmail(address="jdoe@example.com", primary=True)],
- )
-
- # String to identify that the data came from your system. For example, your company name.
- source = "BLUELINK-PARSONS-TEST"
-
- # call upsert_person
- self.bluelink.upsert_person(source, person)
-
- def test_table_to_people(self):
- """
- Test transforming a parsons Table -> list[BluelinkPerson]
- """
- # setup
- tbl = self.get_table()
-
- # function under test
- actual_people = BluelinkPerson.from_table(tbl, self.row_to_person)
-
- # expected:
- person1 = BluelinkPerson(
- identifiers=[
- BluelinkIdentifier(source="FAKESOURCE", identifier="bart@springfield.net")
- ],
- emails=[BluelinkEmail(address="bart@springfield.net", primary=True)],
- family_name="Simpson",
- given_name="Bart",
- )
- person2 = BluelinkPerson(
- identifiers=[
- BluelinkIdentifier(source="FAKESOURCE", identifier="homer@springfield.net")
- ],
- emails=[BluelinkEmail(address="homer@springfield.net", primary=True)],
- family_name="Simpson",
- given_name="Homer",
- )
- expected_people = [person1, person2]
- self.assertEqual(actual_people, expected_people)