-
Notifications
You must be signed in to change notification settings - Fork 134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Kasiah/community dot com connector #1112
Merged
Merged
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
7f4241f
build out community.com connector
KasiaHinkson 277b159
fix url
KasiaHinkson 439d053
add to init
KasiaHinkson bc49be2
get not post
KasiaHinkson efbf3d4
small edits to fix connector
KasiaHinkson 657b86e
Update community.py
KasiaHinkson 528e2df
change some names and add test
KasiaHinkson 27dbb09
fix param name
KasiaHinkson d0a31bd
fix url
KasiaHinkson bd593ce
wrong url again
KasiaHinkson c8438e9
maybe this will magically work
KasiaHinkson bbc1518
add a log for test
KasiaHinkson 71489d9
another log
KasiaHinkson 48fb07a
another log
KasiaHinkson 98fec87
trying again
KasiaHinkson 5b9e0d6
will this pass
KasiaHinkson ed8e712
more stuff
KasiaHinkson b62a481
change test response
KasiaHinkson fc2551e
content not json
KasiaHinkson 4f27edc
another log
KasiaHinkson 36b0651
more logs
KasiaHinkson d052f40
different test values
KasiaHinkson 83f8674
shorten the test string
KasiaHinkson 126453a
values don't matter
KasiaHinkson 8877778
cleanup
KasiaHinkson 2ae666f
docs
KasiaHinkson d868766
fix link
KasiaHinkson ab5b88f
remove commented code
KasiaHinkson d07daab
don't need these
KasiaHinkson 96af8fa
remove another thing
KasiaHinkson 82576c6
linting
KasiaHinkson 0e6667d
incorporate sharine's fix with an extra try except
KasiaHinkson 22f8841
update links
KasiaHinkson 5d6c17c
links
KasiaHinkson File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from parsons.community.community import Community | ||
|
||
__all__ = ["Community"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import logging | ||
from parsons.utilities.api_connector import APIConnector | ||
from parsons.utilities import check_env | ||
from parsons.etl import Table | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
COMMUNITY_API_ENDPOINT = "https://dl.community.com/download/v1/files/" | ||
|
||
|
||
class Community(object): | ||
""" | ||
Instantiate class. | ||
|
||
`Args:` | ||
community_client_id: str | ||
The Community provided Client ID. Not required if ``COMMUNITY_CLIENT_ID`` env | ||
variable set. | ||
community_access_token: str | ||
The Community provided access token. Not required if ``COMMUNITY_ACCESS_TOKEN`` env | ||
variable set. | ||
community_uri: str | ||
The URI to access the API. Not required, default is | ||
`<https://dl.community.com/download/v1/files/>`_. You can set an ``COMMUNITY_URL`` env | ||
variable or use this URI parameter if a different endpoint is necessary. | ||
|
||
`API Documentation <https://developer.community.com/reference/data-export-api-downloading-data>`_ | ||
""" | ||
|
||
def __init__(self, community_client_id=None, community_access_token=None, community_url=None): | ||
self.community_client_id = check_env.check("community_client_id", community_client_id) | ||
self.community_access_token = check_env.check( | ||
"community_access_token", community_access_token | ||
) | ||
self.uri = ( | ||
check_env.check("COMMUNITY_URL", community_url, optional=True) | ||
or f"{COMMUNITY_API_ENDPOINT}/{community_client_id}/" | ||
) | ||
self.headers = { | ||
"Authorization": f"Bearer {self.community_access_token}", | ||
} | ||
self.client = APIConnector( | ||
self.uri, | ||
headers=self.headers, | ||
) | ||
|
||
def get_request(self, filename): | ||
""" | ||
GET request to Community.com API to get the CSV data. | ||
|
||
`Args:` | ||
filename: str | ||
Data filename you are requesting. | ||
Options: | ||
'campaigns': Campaign Performance data | ||
'outbound_message_type_usage`: Message Segment Usage data | ||
'campaign_links': Campaign Link Performance data | ||
'members': Member Details data | ||
'member_state_changes': Member Subscription Status data | ||
'custom_member_data': Custom Member Data | ||
'communities': Communities data | ||
'member_communities': Member Communities data | ||
|
||
`Returns:` | ||
Response of GET request; a successful response returns the CSV formatted data | ||
""" | ||
|
||
logger.info(f"Requesting {filename}") | ||
url = ( | ||
f"{filename}.csv.gz" | ||
if filename != "outbound_message_type_usage" | ||
else f"{filename}.csv.gz/segment-based-subscription" | ||
) | ||
response = self.client.get_request(url=url, return_format="content") | ||
return response | ||
|
||
def get_data_export(self, filename): | ||
""" | ||
Get specified data from Community.com API as Parsons table. | ||
|
||
`Args:` | ||
filename: str | ||
Data filename you are requesting. | ||
Options: | ||
'campaigns': Campaign Performance data | ||
'outbound_message_type_usage`: Message Segment Usage data | ||
'campaign_links': Campaign Link Performance data | ||
'members': Member Details data | ||
'member_state_changes': Member Subscription Status data | ||
'custom_member_data': Custom Member Data | ||
'communities': Communities data | ||
'member_communities': Member Communities data | ||
|
||
`Returns:` | ||
Contents of the generated contribution CSV as a Parsons table. | ||
""" | ||
|
||
get_request_response = self.get_request(filename=filename) | ||
response_string = get_request_response.decode("utf-8") | ||
table = Table.from_csv_string(response_string) | ||
return table |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import unittest | ||
import requests_mock | ||
from parsons import Community | ||
|
||
|
||
TEST_CLIENT_ID = "someuuid" | ||
TEST_CLIENT_TOKEN = "somesecret" | ||
|
||
TEST_FILENAME = "campaigns" | ||
TEST_URI = f"https://faketestingurl.com/{TEST_CLIENT_ID}" | ||
TEST_FULL_URL = f"{TEST_URI}/{TEST_FILENAME}.csv.gz" | ||
|
||
TEST_GET_RESPONSE_CSV_STRING = b'"CAMPAIGN_ID","LEADER_ID"\n"0288","6e83b"\n' | ||
|
||
TEST_EXPECTED_COLUMNS = [ | ||
"CAMPAIGN_ID", | ||
"LEADER_ID", | ||
] | ||
|
||
|
||
class TestCommunity(unittest.TestCase): | ||
@requests_mock.Mocker() | ||
def setUp(self, m): | ||
self.com = Community(TEST_CLIENT_ID, TEST_CLIENT_TOKEN, TEST_URI) | ||
|
||
@requests_mock.Mocker() | ||
def test_successful_get_request(self, m): | ||
m.get(TEST_FULL_URL, content=TEST_GET_RESPONSE_CSV_STRING) | ||
|
||
assert self.com.get_request(filename=TEST_FILENAME) == TEST_GET_RESPONSE_CSV_STRING | ||
|
||
# test get resource | ||
@requests_mock.Mocker() | ||
def test_successful_get_data_export(self, m): | ||
m.get(TEST_FULL_URL, content=TEST_GET_RESPONSE_CSV_STRING) | ||
|
||
table = self.com.get_data_export( | ||
TEST_FILENAME, | ||
) | ||
assert TEST_EXPECTED_COLUMNS == table.columns |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change looks okay to me, I'm curious about the reasoning? That will help me understand how big the breaking change would be - right now this code is so similar that it's hard to tell how it might cause problems for downstream users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reasoning for the change is that something about how our Parsons operator is working is changing the attributes of the CSVView, and we just need to do the
to_csv
instead of getting the filename. It would presumably break for someone if they want it to fail when encountering an Attribute Error. I assume this is unlikely and that this probably wouldn't really break anything for anyone, but technically if someone is depending on getting an AttributeError in some case, this would mean they instead get an actual table. Not sure why that would be a problem, but trying to be proactive!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay gotcha, that makes sense. Yeah, it's hard to imagine someone depending on getting an error there - there's no reason to call the function at all if you error out there. It hasn't uploaded the data to the blob yet, and it's not saving anything on the object that you can access, so there's no reason to call the function and catch the attribute error unless you're checking if
table.table.source.filename
exists, doing something, then calling the function again. But that would be a very weird pattern, like you could literally just check iftable.table.source.filename
exists directly. So yes, this is technically a breaking change but I think the chances of anyone being bothered by it are very very small so we are okay to merge it into main instead of major release.