def fetch(settings: typing.Dict[str, typing.Any], since: datetime.date, until: datetime.date) -> Report: """Fetch Toggle report.""" LOGGER.info( 'Fetching Toggl summary report: since=%s, until=%s, workspace_id=%s', since, until, settings.get('workspace_id')) client = TogglClientApi(settings) params = settings params.pop('token') params['since'] = str(since) params['until'] = str(until) params['grouping'] = 'projects' params['subgrouping'] = 'time_entries' response = client.query_report('/summary', params=params) response_data = response.json() if response.status_code != 200: raise RuntimeError( 'Cannot fetch Toggl report: {}'.format(response_data)) LOGGER.debug('Response: %s', pprint.pformat(response_data)) total_grand = response_data['total_grand'] if not total_grand: raise RuntimeError('Toggl returned no data: {}'.format(response_data)) hours = Decimal(total_grand) / Decimal(1_000 * 60 * 60) report = Report(hours=hours) LOGGER.info('Fetched report: %s', report) return report
def test_overriding_default_base_url_and_version_on_instance_creation(self): my_base_url = 'http://myownapi.com' my_ver_no = 12 credentials = { 'base_url': my_base_url, 'ver_api': my_ver_no } self.api = TogglClientApi(credentials) self.assertEqual(self.api.api_base_url, my_base_url + '/v' + str(my_ver_no))
def setUp(self): self.settings = { 'workspace_name': 'A Company', 'token': 'xxx', 'base_url': 'https://my.service/api', 'ver_api': 8, 'base_url_report': 'https://my.service/reports/api', 'ver_report': 2 } self.api = TogglClientApi(self.settings) httpretty.enable() # enable HTTPretty so that it will monkey patch the socket module self.base_api_url = self.api.build_api_url(self.settings['base_url'], self.settings['ver_api']) self.base_api_report_url = self.api.build_api_url(self.settings['base_url_report'], self.settings['ver_report'])
def __init__(self, look_back_hours: int = 8, event=None) -> None: self.look_back_hours = look_back_hours self.time_from: str = None # Format 2021-02-13T08:27:13.772498Z self.time_to: str = None self.timezone: timezone = timezone("Europe/Budapest") # Load credentials try: with open('token.pickle', 'rb') as token: self.creds, self.toogle_settings = pickle.load(token) self.toggl_client = TogglClientApi(self.toogle_settings) except FileNotFoundError: print( "Credentials does not exist. Please genrate token.pickle first." ) # Setting lookback timeframe self._set_hours_param(event) self._calculate_from_to_timestamps()
def setUp(self): self.settings = { "workspace_name": "A Company", "token": "xxx", "base_url": "https://my.service/api", "ver_api": 8, "base_url_report": "https://my.service/reports/api", "ver_report": 2, } self.api = TogglClientApi(self.settings) httpretty.enable() # enable HTTPretty so that it will monkey patch the socket module self.base_api_url = self.api.build_api_url(self.settings["base_url"], self.settings["ver_api"]) self.base_api_report_url = self.api.build_api_url(self.settings["base_url_report"], self.settings["ver_report"])
class TogglClientApiLiveTests(unittest.TestCase): api = None # Runs before *every* tests def setUp(self): file_contents = open('tests_live_config.json') self.settings = json.load(file_contents) file_contents.close() self.api = TogglClientApi(self.settings) # Runs after *every* tests def tearDown(self): del self.api def test_api_client_instance_created(self): self.assertNotEqual(self.api, None) def test_valid_toggl_base_url(self): self.assertEqual(self.api.api_base_url, 'https://www.toggl.com/api/v8') def test_api_toggl_auth_check_response_ok(self): response = self.api.query('/me') self.assertEqual(response.status_code, 200) def test_api_toggl_get_list_of_workspaces_response_ok(self): response = self.api.get_workspaces() self.assertEqual(response.status_code, 200) def test_api_toggl_get_workspace_by_name_found(self): workspace = self.api.get_workspace_by_name(self.settings['workspace_name']) self.assertNotEqual(workspace['id'], '') def test_api_toggl_get_workspace_members_response_ok(self): workspace = self.api.get_workspace_by_name(self.settings['workspace_name']) response = self.api.get_workspace_members(workspace['id']) self.assertEqual(response.status_code, 200) def test_api_toggl_get_member_total_hours_range_response_ok(self): workspace_id = self.settings['workspace_id'] user_id = self.settings['user_id'] start_date = self.settings['start_date'] end_date = self.settings['end_date'] total = self.api.get_user_hours_range( 'toggl-python-api-client-nosetests', workspace_id, user_id, start_date, end_date ) self.assertGreater(total, 0)
class ToogleClientApiTests(unittest.TestCase): api = None connectLive = False base_api_url = "" base_api_report_url = "" http_content_type = "application/json" # Runs before *every* tests def setUp(self): self.settings = { "workspace_name": "A Company", "token": "xxx", "base_url": "https://my.service/api", "ver_api": 8, "base_url_report": "https://my.service/reports/api", "ver_report": 2, } self.api = TogglClientApi(self.settings) httpretty.enable() # enable HTTPretty so that it will monkey patch the socket module self.base_api_url = self.api.build_api_url(self.settings["base_url"], self.settings["ver_api"]) self.base_api_report_url = self.api.build_api_url(self.settings["base_url_report"], self.settings["ver_report"]) # Runs after *every* tests def tearDown(self): del self.api httpretty.disable() httpretty.reset() def load_json_file(self, location, base_path="tests/json_responses"): file_contents = open(base_path + "/" + location + ".json") json_data = json.load(file_contents) file_contents.close() return json_data def test_api_client_instance_created(self): self.assertNotEqual(self.api, None) def test_overriding_default_base_url_and_version_on_instance_creation(self): my_base_url = "http://myownapi.com" my_ver_no = 12 credentials = {"base_url": my_base_url, "ver_api": my_ver_no} self.api = TogglClientApi(credentials) self.assertEqual(self.api.api_base_url, my_base_url + "/v" + str(my_ver_no)) def test_api_token_set(self): self.assertNotEqual(self.api.api_token, "") def test_api_toggl_auth_response(self): expected_response_json_str = json.dumps(self.load_json_file("me")) httpretty.register_uri( httpretty.GET, self.base_api_url + "/me", body=expected_response_json_str, content_type=self.http_content_type, ) response = self.api.query("/me") self.assertEqual(response.status_code, 200) self.assertEqual(response.text, expected_response_json_str) def setup_test_api_toggl_get_workspace(self, json_file="workspaces"): expected_response_json_str = json.dumps(self.load_json_file(json_file)) httpretty.register_uri( httpretty.GET, self.base_api_url + "/workspaces", body=expected_response_json_str, content_type=self.http_content_type, ) return expected_response_json_str def test_api_toggl_get_list_of_workspaces_response(self): expected_response_json_str = self.setup_test_api_toggl_get_workspace() response = self.api.get_workspaces() self.assertEqual(response.status_code, 200) self.assertEqual(response.text, expected_response_json_str) def setup_test_api_toggl_get_workspace_by_name(self, expected_found_index=2, json_file="workspaces"): expected_response_json_str = self.setup_test_api_toggl_get_workspace(json_file) data = json.loads(expected_response_json_str) expected_workspace = data[expected_found_index] workspace = self.api.get_workspace_by_name(self.settings["workspace_name"]) return workspace, expected_workspace def test_api_toggl_get_workspace_by_name_found(self): workspace, expected_workspace = self.setup_test_api_toggl_get_workspace_by_name() self.assertEqual(workspace["id"], expected_workspace["id"]) def setup_test_api_toggl_get_workspace_members(self): workspace, expected_workspace = self.setup_test_api_toggl_get_workspace_by_name() expected_response_json_str = json.dumps(self.load_json_file("workspace_members")) httpretty.register_uri( httpretty.GET, self.base_api_url + "/workspaces/" + str(workspace["id"]) + "/workspace_users", body=expected_response_json_str, content_type=self.http_content_type, ) response = self.api.get_workspace_members(workspace["id"]) return response, expected_response_json_str, workspace, expected_workspace def test_api_toggl_get_workspace_members_response_ok(self): response, expected_response_json_str, workspace, expected_workspace = ( self.setup_test_api_toggl_get_workspace_members() ) self.assertEqual(workspace["id"], expected_workspace["id"]) self.assertEqual(response.status_code, 200) def test_api_toggl_get_workspace_members_response_count(self): response, expected_response_json_str, workspace, expected_workspace = ( self.setup_test_api_toggl_get_workspace_members() ) self.assertEqual(workspace["id"], expected_workspace["id"]) received_data = response.json() expected_data = json.loads(expected_response_json_str) self.assertEqual(len(received_data), len(expected_data)) def test_api_toggl_get_member_total_hours_range_response_ok_with_found_data(self): self.do_test_api_toggl_get_member_total_hours_range_response_ok("report_user_project_hours") def test_api_toggl_get_member_total_hours_range_response_ok_with_empty_data(self): self.do_test_api_toggl_get_member_total_hours_range_response_ok("report_user_project_hours_null") def do_test_api_toggl_get_member_total_hours_range_response_ok(self, datafile="report_user_project_hours"): workspace, expected_workspace = self.setup_test_api_toggl_get_workspace_by_name() self.assertEqual(workspace["id"], expected_workspace["id"]) workspace_id = workspace["id"] user_id = 222222 start_date = "2014-03-03" end_date = "2014-03-07" user_agent = "toggl-python-api-client-nosetests" endpoint_url = ( self.base_api_report_url + "/summary?" + "workspace_id=" + str(workspace["id"]) + "&since=" + start_date + "&until=" + end_date + "&user_agent=" + user_agent + "&user_ids=" + str(user_id) + "&grouping=users" + "&subgrouping=projects" ) expected_response_json_str = json.dumps(self.load_json_file(datafile)) httpretty.register_uri( httpretty.GET, endpoint_url, body=expected_response_json_str, content_type=self.http_content_type ) expected_response_json = json.loads(expected_response_json_str) if len(expected_response_json["data"]) > 0: expected_total = expected_response_json["data"][0]["time"] else: expected_total = 0 total = self.api.get_user_hours_range(user_agent, workspace_id, user_id, start_date, end_date) self.assertEqual(total, expected_total)
def test_overriding_default_base_url_and_version_on_instance_creation(self): my_base_url = "http://myownapi.com" my_ver_no = 12 credentials = {"base_url": my_base_url, "ver_api": my_ver_no} self.api = TogglClientApi(credentials) self.assertEqual(self.api.api_base_url, my_base_url + "/v" + str(my_ver_no))
def setUp(self): file_contents = open('tests_live_config.json') self.settings = json.load(file_contents) file_contents.close() self.api = TogglClientApi(self.settings)
class Calender2Toggl(): def __init__(self, look_back_hours: int = 8, event=None) -> None: self.look_back_hours = look_back_hours self.time_from: str = None # Format 2021-02-13T08:27:13.772498Z self.time_to: str = None self.timezone: timezone = timezone("Europe/Budapest") # Load credentials try: with open('token.pickle', 'rb') as token: self.creds, self.toogle_settings = pickle.load(token) self.toggl_client = TogglClientApi(self.toogle_settings) except FileNotFoundError: print( "Credentials does not exist. Please genrate token.pickle first." ) # Setting lookback timeframe self._set_hours_param(event) self._calculate_from_to_timestamps() def _set_hours_param(self, event=None): """ Sets look_back_hours parameter from Pub/Sub trigger message""" try: if 'data' in event: self.look_back_hours = int( base64.b64decode(event['data']).decode('utf-8')) except Exception: pass def _calculate_from_to_timestamps(self) -> None: """Calculates date ranges within calendar events will be querried.""" now = datetime.datetime.utcnow() time_to = now.isoformat() + 'Z' # 'Z' indicates UTC time time_from = (now - datetime.timedelta(hours=self.look_back_hours)).isoformat() + \ 'Z' # 'Z' indicates UTC time self.time_from, self.time_to = time_from, time_to def _get_toggl_projects(self): return self.toggl_client.get_projects().json() def _get_calendar_events(self) -> List[Dict]: """Querries events from Google Calendar Returns: event [List[Dict]]: Google Calendar events as list of dicts. """ service = build('calendar', 'v3', credentials=self.creds, cache_discovery=False) cal_events_result = service.events().list( calendarId='primary', timeMin=self.time_from, timeMax=self.time_to, maxResults=None, singleEvents=True, orderBy='startTime').execute() return cal_events_result.get('items', []) def _query_existing_toggl_items(self) -> List[Dict]: """Query existing time entries in toggl to avoid duplicates""" auth = HTTPBasicAuth(self.toogle_settings['token'], 'api_token') end_tm = datetime.datetime.now().astimezone( self.timezone).replace(microsecond=0) start_tm = end_tm - datetime.timedelta(hours=self.look_back_hours + 3) url_schema = { "start_date": start_tm.isoformat(), "end_date": end_tm.isoformat() } url = "https://api.track.toggl.com/api/v8/time_entries?" + urlencode( url_schema) return requests.get(url, auth=auth).json() @staticmethod def load_event(row, toggl_client): """Parses and loads events to toggl in the right format. """ try: new_entry = { "time_entry": { "description": row['description'], "tags": [], "duration": (row['end_tm'] - row['start_tm']).seconds, "start": row['start_tm'].isoformat(), "stop": row['end_tm'].isoformat(), "pid": row['id'], "created_with": "calendar2toggl_app" } } toggl_client.create_time_entry(new_entry) print(f"Event loaded: {row['summary']}") return 'uploaded' except Exception as e: print(f"Failed to load: {row}") print(e) return 'failed to upload' def load_to_toggl(self, calendar_events: pd.DataFrame) -> None: """Uploads calendar events to toggl Args: calendar_events (pd.DataFrame): dataframe containing events to be uploaded """ # Load events to toogle if calendar_events.shape[0] > 0: calendar_events.apply(self.load_event, axis=1, args=[self.toggl_client]) else: print( f'No events found between {self.time_from} and {self.time_to}.' )
class ToogleClientApiTests(unittest.TestCase): api = None connectLive = False base_api_url = '' base_api_report_url = '' http_content_type = "application/json" # Runs before *every* tests def setUp(self): self.settings = { 'workspace_name': 'A Company', 'token': 'xxx', 'base_url': 'https://my.service/api', 'ver_api': 8, 'base_url_report': 'https://my.service/reports/api', 'ver_report': 2 } self.api = TogglClientApi(self.settings) httpretty.enable() # enable HTTPretty so that it will monkey patch the socket module self.base_api_url = self.api.build_api_url(self.settings['base_url'], self.settings['ver_api']) self.base_api_report_url = self.api.build_api_url(self.settings['base_url_report'], self.settings['ver_report']) # Runs after *every* tests def tearDown(self): del self.api httpretty.disable() httpretty.reset() def load_json_file(self, location, base_path='tests/json_responses'): file_contents = open(base_path+'/'+location+'.json') json_data = json.load(file_contents) file_contents.close() return json_data def test_api_client_instance_created(self): self.assertNotEqual(self.api, None) def test_overriding_default_base_url_and_version_on_instance_creation(self): my_base_url = 'http://myownapi.com' my_ver_no = 12 credentials = { 'base_url': my_base_url, 'ver_api': my_ver_no } self.api = TogglClientApi(credentials) self.assertEqual(self.api.api_base_url, my_base_url + '/v' + str(my_ver_no)) def test_api_token_set(self): self.assertNotEqual(self.api.api_token, '') def test_api_toggl_auth_response(self): expected_response_json_str = json.dumps(self.load_json_file('me')) httpretty.register_uri( httpretty.GET, self.base_api_url + "/me", body=expected_response_json_str, content_type=self.http_content_type) response = self.api.query('/me') self.assertEqual(response.status_code, 200) self.assertEqual(response.text, expected_response_json_str) def setup_test_api_toggl_get_workspace(self, json_file='workspaces'): expected_response_json_str = json.dumps(self.load_json_file(json_file)) httpretty.register_uri( httpretty.GET, self.base_api_url + "/workspaces", body=expected_response_json_str, content_type=self.http_content_type) return expected_response_json_str def test_api_toggl_get_list_of_workspaces_response(self): expected_response_json_str = self.setup_test_api_toggl_get_workspace() response = self.api.get_workspaces() self.assertEqual(response.status_code, 200) self.assertEqual(response.text, expected_response_json_str) def setup_test_api_toggl_get_workspace_by_name(self, expected_found_index=2, json_file='workspaces'): expected_response_json_str = self.setup_test_api_toggl_get_workspace(json_file) data = json.loads(expected_response_json_str) expected_workspace = data[expected_found_index] workspace = self.api.get_workspace_by_name(self.settings['workspace_name']) return workspace, expected_workspace def test_api_toggl_get_workspace_by_name_found(self): workspace, expected_workspace = self.setup_test_api_toggl_get_workspace_by_name() self.assertEqual(workspace['id'], expected_workspace['id']) def setup_test_api_toggl_get_workspace_members(self): workspace, expected_workspace = self.setup_test_api_toggl_get_workspace_by_name() expected_response_json_str = json.dumps(self.load_json_file('workspace_members')) httpretty.register_uri( httpretty.GET, self.base_api_url + "/workspaces/" + str(workspace['id']) + "/workspace_users", body=expected_response_json_str, content_type=self.http_content_type) response = self.api.get_workspace_members(workspace['id']) return response, expected_response_json_str, workspace, expected_workspace def test_api_toggl_get_workspace_members_response_ok(self): response, expected_response_json_str, workspace, expected_workspace = self.setup_test_api_toggl_get_workspace_members() self.assertEqual(workspace['id'], expected_workspace['id']) self.assertEqual(response.status_code, 200) def test_api_toggl_get_workspace_members_response_count(self): response, expected_response_json_str, workspace, expected_workspace = self.setup_test_api_toggl_get_workspace_members() self.assertEqual(workspace['id'], expected_workspace['id']) received_data = response.json() expected_data = json.loads(expected_response_json_str) self.assertEqual(len(received_data), len(expected_data)) def test_api_toggl_get_member_total_hours_range_response_ok_with_found_data(self): self.do_test_api_toggl_get_member_total_hours_range_response_ok('report_user_project_hours') def test_api_toggl_get_member_total_hours_range_response_ok_with_empty_data(self): self.do_test_api_toggl_get_member_total_hours_range_response_ok('report_user_project_hours_null') def do_test_api_toggl_get_member_total_hours_range_response_ok(self, datafile='report_user_project_hours'): workspace, expected_workspace = self.setup_test_api_toggl_get_workspace_by_name() self.assertEqual(workspace['id'], expected_workspace['id']) workspace_id = workspace['id'] user_id = 222222 start_date = '2014-03-03' end_date = '2014-03-07' user_agent = 'toggl-python-api-client-nosetests' endpoint_url = self.base_api_report_url + "/summary?" + "workspace_id=" + str(workspace['id']) + "&since=" + start_date + "&until=" + end_date + "&user_agent=" + user_agent + "&user_ids=" + str(user_id) + "&grouping=users" + "&subgrouping=projects" expected_response_json_str = json.dumps(self.load_json_file(datafile)) httpretty.register_uri( httpretty.GET, endpoint_url, body=expected_response_json_str, content_type=self.http_content_type) expected_response_json = json.loads(expected_response_json_str) if len(expected_response_json['data']) > 0: expected_total = expected_response_json['data'][0]['time'] else: expected_total = 0 total = self.api.get_user_hours_range( user_agent, workspace_id, user_id, start_date, end_date ) self.assertEqual(total, expected_total)
def get_client(self): settings = self.get_credentials() return TogglClientApi(settings)