-
Notifications
You must be signed in to change notification settings - Fork 11
[TDL-15656] Added Support for dev mode #41
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
base: master
Are you sure you want to change the base?
Changes from 3 commits
cccfc1b
d586f25
2581588
4ad0dac
1481ba4
e6ee98b
9099b0b
5b0690b
eddf3ba
2ae8cb3
39c9754
59c5b74
330d464
264e0d0
cb905b0
31de7b0
e02e8a1
d75aac7
53ff291
5269472
475dff9
6021e28
13bcef3
e1cbe5b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,28 +1,36 @@ | ||||||||||
| import json | ||||||||||
| from datetime import datetime, timedelta | ||||||||||
| import sys | ||||||||||
|
|
||||||||||
| import backoff | ||||||||||
| import requests | ||||||||||
| from requests.exceptions import ConnectionError | ||||||||||
| from singer import metrics | ||||||||||
| from singer import metrics,get_logger | ||||||||||
| from singer.utils import strptime_to_utc,now,strftime | ||||||||||
| from .utils import read_config,write_config | ||||||||||
| LOGGER = get_logger() | ||||||||||
|
|
||||||||||
|
|
||||||||||
| class Server5xxError(Exception): | ||||||||||
| pass | ||||||||||
|
|
||||||||||
|
|
||||||||||
| class EloquaClient(object): | ||||||||||
| def __init__(self, | ||||||||||
| config_path, | ||||||||||
| client_id, | ||||||||||
| client_secret, | ||||||||||
| refresh_token, | ||||||||||
| redirect_uri, | ||||||||||
| user_agent): | ||||||||||
| user_agent, | ||||||||||
| dev_mode=False): | ||||||||||
| self.__config_path = config_path | ||||||||||
| self.__client_id = client_id | ||||||||||
| self.__client_secret = client_secret | ||||||||||
| self.__refresh_token = refresh_token | ||||||||||
| self.__redirect_uri = redirect_uri | ||||||||||
| self.__user_agent = user_agent | ||||||||||
| self.dev_mode = dev_mode | ||||||||||
| self.__access_token = None | ||||||||||
| self.__expires = None | ||||||||||
| self.__session = requests.Session() | ||||||||||
|
|
@@ -40,7 +48,21 @@ def __exit__(self, type, value, traceback): | |||||||||
| max_tries=5, | ||||||||||
| factor=2) | ||||||||||
| def get_access_token(self): | ||||||||||
| if self.__access_token is not None and self.__expires > datetime.utcnow(): | ||||||||||
| if self.dev_mode: | ||||||||||
| config = read_config(self.__config_path) | ||||||||||
| try: | ||||||||||
| self.__access_token = config['access_token'] | ||||||||||
| self.__refresh_token = config['refresh_token'] | ||||||||||
| self.__expires=strptime_to_utc(config['expires_in']) | ||||||||||
| except KeyError as _: | ||||||||||
|
vishalp-dev marked this conversation as resolved.
Outdated
|
||||||||||
| LOGGER.fatal("Unable to locate key %s in config",_) | ||||||||||
| raise Exception("Unable to locate key in config") | ||||||||||
| if self.__access_token is not None and self.__expires > now(): | ||||||||||
|
vishalp-dev marked this conversation as resolved.
Outdated
|
||||||||||
| return | ||||||||||
| LOGGER.fatal("Access Token in config is expired, unable to authenticate in dev mode") | ||||||||||
| raise Exception("Access Token in config is expired, unable to authenticate in dev mode") | ||||||||||
|
|
||||||||||
| if self.__access_token is not None and self.__expires > now(): | ||||||||||
|
vishalp-dev marked this conversation as resolved.
Outdated
|
||||||||||
| return | ||||||||||
|
|
||||||||||
| headers = {} | ||||||||||
|
|
@@ -70,19 +92,14 @@ def get_access_token(self): | |||||||||
| eloqua_response)) | ||||||||||
|
|
||||||||||
| data = response.json() | ||||||||||
|
|
||||||||||
| self.__access_token = data['access_token'] | ||||||||||
| self.__refresh_token = data['refresh_token'] | ||||||||||
|
|
||||||||||
| ## refresh_token rotates on every reauth | ||||||||||
| with open(self.__config_path) as file: | ||||||||||
| config = json.load(file) | ||||||||||
| config['refresh_token'] = data['refresh_token'] | ||||||||||
| with open(self.__config_path, 'w') as file: | ||||||||||
| json.dump(config, file, indent=2) | ||||||||||
|
|
||||||||||
|
vishalp-dev marked this conversation as resolved.
|
||||||||||
| expires_seconds = data['expires_in'] - 10 # pad by 10 seconds | ||||||||||
|
vishalp-dev marked this conversation as resolved.
Outdated
|
||||||||||
| self.__expires = datetime.utcnow() + timedelta(seconds=expires_seconds) | ||||||||||
| self.__expires = now() + timedelta(seconds=expires_seconds) | ||||||||||
|
|
||||||||||
| if not self.dev_mode: | ||||||||||
| update_config_keys = {"refresh_token":self.__refresh_token,"access_token":self.__access_token,"expires_in": strftime(self.__expires)} | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reduce the statement length. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||||||||||
| config = write_config(self.__config_path,update_config_keys) | ||||||||||
|
|
||||||||||
| def get_base_urls(self): | ||||||||||
| data = self.request('GET', | ||||||||||
|
|
@@ -135,3 +152,4 @@ def get(self, path, **kwargs): | |||||||||
|
|
||||||||||
| def post(self, path, **kwargs): | ||||||||||
| return self.request('POST', path=path, **kwargs) | ||||||||||
|
|
||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove this extra line.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| from typing import Dict | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is unused |
||
| import json | ||
| from singer import get_logger | ||
|
|
||
| LOGGER = get_logger() | ||
|
|
||
| def read_config(config_path) -> Dict: | ||
|
vishalp-dev marked this conversation as resolved.
Outdated
|
||
| try: | ||
| with open(config_path,'r') as tap_config: | ||
| return json.load(tap_config) | ||
| except FileNotFoundError as _: | ||
| LOGGER.fatal("Failed to load config in dev mode") | ||
| return {} | ||
|
|
||
|
|
||
| def write_config(config_path,data :Dict) -> Dict: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please check this comment on tap-deputy: singer-io/tap-deputy#3 (comment)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. made required changes |
||
| config = read_config(config_path) | ||
| config.update(data) | ||
| with open(config_path,'w') as tap_config: | ||
| json.dump(config, tap_config, indent=2) | ||
| return config | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,102 @@ | ||||||
| import json | ||||||
| import unittest | ||||||
| from tap_eloqua.client import EloquaClient, strftime | ||||||
| from unittest.mock import patch | ||||||
| import datetime | ||||||
| import pytz | ||||||
|
|
||||||
|
|
||||||
| class MockResponse: | ||||||
| def __init__(self, json_data, status_code): | ||||||
| self.json_data = json_data | ||||||
| self.status_code = status_code | ||||||
|
|
||||||
| def json(self): | ||||||
| return self.json_data | ||||||
|
|
||||||
|
|
||||||
| def mocked_auth_post(*args, **kwargs): | ||||||
| return MockResponse({"access_token": "hellothere", "refresh_token": "hello", "expires_in": 5000}, status_code=200) | ||||||
|
|
||||||
|
|
||||||
| class Test_ClientDevMode(unittest.TestCase): | ||||||
| """Test the dev mode functionality""" | ||||||
|
|
||||||
| base_config = {"client_id": "", | ||||||
| "client_secret": "", | ||||||
| "refresh_token": "", | ||||||
| "redirect_uri": "", | ||||||
| "user_agent": "", } | ||||||
| tmp_config_path = "/tmp/eloqua_config.json" | ||||||
|
|
||||||
|
|
||||||
| @patch("requests.Session.post", side_effect=mocked_auth_post) | ||||||
| def test_client_without_dev_mode(self, mocked_auth_post): | ||||||
| """checks if the client can write refresh token and expiry to config""" | ||||||
|
|
||||||
| config_sample = self.base_config | ||||||
|
|
||||||
| with open(self.tmp_config_path, "w") as ff: | ||||||
| json.dump(config_sample, ff) | ||||||
|
|
||||||
| params = {"config_path": self.tmp_config_path, "dev_mode": False} | ||||||
| params.update(self.base_config) | ||||||
|
|
||||||
| client = EloquaClient(**params) | ||||||
| client.get_access_token() | ||||||
| with open(self.tmp_config_path, "r") as config_file: | ||||||
| config = json.load(config_file) | ||||||
| self.assertEqual(True, "expires_in" in config) | ||||||
| self.assertEqual(True, "access_token" in config) | ||||||
| self.assertEqual(True, "refresh_token" in config) | ||||||
|
Comment on lines
+62
to
+64
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there are 2 better options here:
|
||||||
|
|
||||||
| def test_devmode_accesstoken_absent(self, *args, **kwargs): | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like |
||||||
| """ | ||||||
| checks exception if access token is not present in config | ||||||
| """ | ||||||
| config_sample = self.base_config | ||||||
|
|
||||||
| with open(self.tmp_config_path, "w") as config_file: | ||||||
| json.dump(config_sample, config_file) | ||||||
|
|
||||||
| params = {"config_path": self.tmp_config_path, "dev_mode": True} | ||||||
| params.update(self.base_config) | ||||||
| try: | ||||||
| client = EloquaClient(**params) | ||||||
| client.get_access_token() | ||||||
| except Exception as _: | ||||||
| self.assertEqual(str(_), "Unable to locate key in config") | ||||||
|
|
||||||
| @patch("requests.Session.post", side_effect=mocked_auth_post) | ||||||
| def test_client_valid_token(self, mocked_auth_post): | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| """checks if the client can fetch and validate refresh token and expiry from config""" | ||||||
|
|
||||||
| self.tmp_config_path = "/tmp/eloqua_config.json" | ||||||
| config_sample = { | ||||||
| "access_token": "hellothere", | ||||||
| "expires_in": strftime(datetime.datetime.utcnow().replace(tzinfo=pytz.UTC) + datetime.timedelta(minutes=5))} | ||||||
| config_sample.update(self.base_config) | ||||||
| with open(self.tmp_config_path, "w") as config_file: | ||||||
| json.dump(config_sample, config_file) | ||||||
| params = {"config_path": self.tmp_config_path, "dev_mode": True} | ||||||
| params.update(self.base_config) | ||||||
| EloquaClient(**params).get_access_token() | ||||||
|
|
||||||
|
|
||||||
| @patch("requests.Session.post", side_effect=mocked_auth_post) | ||||||
| def test_client_invalid_token(self, mocked_auth_post): | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| """checks if the client can fetch and validate refresh token and expiry from config""" | ||||||
|
|
||||||
| config_sample = { | ||||||
| "access_token": "hellothere", | ||||||
| "expires_in": strftime(datetime.datetime.utcnow().replace(tzinfo=pytz.UTC) - datetime.timedelta(minutes=5))} | ||||||
| config_sample.update(self.base_config) | ||||||
| with open(self.tmp_config_path, "w") as config_file: | ||||||
| json.dump(config_sample, config_file) | ||||||
| params = {"config_path": self.tmp_config_path, "dev_mode": True} | ||||||
| params.update(self.base_config) | ||||||
| try: | ||||||
| EloquaClient(**params).get_access_token() | ||||||
| except Exception as _: | ||||||
| self.assertEqual( | ||||||
| str(_), "Access Token in config is expired, unable to authenticate in dev mode") | ||||||
Uh oh!
There was an error while loading. Please reload this page.