From d51185a4372b6ab4b4d4a45c48d535c485f19ff8 Mon Sep 17 00:00:00 2001 From: aarose Date: Fri, 7 Aug 2015 12:21:08 -0400 Subject: [PATCH 01/26] Added tests, tweaked api --- togglwrapper/api.py | 24 +++++++------ togglwrapper/json/user_get.json | 62 +++++++++++++++++++++++++++++++++ togglwrapper/tests.py | 20 +++++++++-- 3 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 togglwrapper/json/user_get.json diff --git a/togglwrapper/api.py b/togglwrapper/api.py index 111029f..a2123d6 100644 --- a/togglwrapper/api.py +++ b/togglwrapper/api.py @@ -7,6 +7,17 @@ API_URL = '{base}/{version}'.format(base=BASE_URL, version=API_VERSION) +def return_json_or_raise_error(func): + def inner(*args, **kwargs): + response = func(*args, **kwargs) + try: + return response.json() + except ValueError: + # JSON couldn't be decoded, raise status error + response.raise_for_status() + return inner + + class TogglObject(object): @property def full_uri(self): @@ -19,10 +30,10 @@ def __init__(self, client): class User(TogglObject): uri = '/me' + @return_json_or_raise_error def get(self, related_data=False, since=None): """ Get the user associated with the current API token. """ - response = requests.get(self.full_uri, auth=self.client.auth) - return response.json() + return requests.get(self.full_uri, auth=self.client.auth) def update(self, data): """ Update the fields. """ @@ -31,14 +42,7 @@ def update(self, data): class Client(object): def __init__(self, api_token, base_url=BASE_URL, version=API_VERSION): - # self.api_token = api_token self.api_url = '{base}/{version}'.format(base=base_url, version=version) self.auth = HTTPBasicAuth(api_token, 'api_token') - self.user = User(self) - - def test_connection(self): - """ Test for a successful connection. """ - test_url = '{base}{uri}'.format(base=self.api_url, uri='test') - response = requests.get(test_url, auth=self.client.auth) - return response.json() + self.User = User(self) diff --git a/togglwrapper/json/user_get.json b/togglwrapper/json/user_get.json new file mode 100644 index 0000000..68ffcde --- /dev/null +++ b/togglwrapper/json/user_get.json @@ -0,0 +1,62 @@ +{"since":1438961718, +"data":{ + "new_blog_post":{}, + "default_wid":979325, + "beginning_of_week":1, + "at":"2015-07-27T15:34:34+00:00", + "api_token":"fake_token_1", + "timezone":"America/New_York", + "timeofday_format":"h:mm A", + "id":1733991, + "retention":9, + "duration_format":"improved", + "timeline_experiment":true, + "send_timer_notifications":true, + "sidebar_piechart":true, + "last_blog_entry":"http://blog.toggl.com/2015/07/new-pro-feature-custom-rates-for-team-members/", + "timeline_enabled":false, + "send_product_emails":true, + "achievements_enabled":true, + "email":"email@domain.com", + "send_weekly_report":true, + "openid_email":"email@domain.com", + "workspaces":[{ + "profile":0, + "rounding_minutes":0, + "premium":false, + "name":"Firstname Lastname's workspace", + "admin":true, + "rounding":1, + "at":"2015-06-10T19:06:43+00:00", + "default_hourly_rate":0, + "ical_enabled":true, + "only_admins_see_team_dashboard":false, + "only_admins_see_billable_rates":false, + "api_token":"fake_token_2", + "projects_billable_by_default":true, + "subscription":{ + "description":"Free" + }, + "only_admins_may_create_projects":false, + "id":979325, + "default_currency":"USD" + }], + "store_start_and_stop_time":true, + "should_upgrade":true, + "fullname":"Firstname Lastname", + "date_format":"MM/DD/YYYY", + "obm":{ + "nr":0 + }, + "render_timeline":true, + "language":"en_US", + "openid_enabled":true, + "record_timeline":false, + "created_at":"2015-06-10T19:06:43+00:00", + "manual_mode":true, + "image_url":"https://assets.toggl.com/images/profile.png", + "invitation":{}, + "jquery_timeofday_format":"h:i A", + "jquery_date_format":"m/d/Y" + } +} diff --git a/togglwrapper/tests.py b/togglwrapper/tests.py index 8f1e42c..8ef3e5b 100644 --- a/togglwrapper/tests.py +++ b/togglwrapper/tests.py @@ -1,18 +1,32 @@ +import json import unittest +from mock import patch +from requests.exceptions import HTTPError + import api class TestAPIMethods(unittest.TestCase): + def setUp(self): + with open('json/user_get.json') as json_data: + self.data = json.load(json_data) + json_data.close() + def test_client(self): """ Should successfully establish the client. """ - api_token = 'correct_token' - self.assertTrue(api.Client(api_token)) + api_token = 'fake_token_1' + toggl = api.Client(api_token) + with patch.object(toggl, 'User') as MockedUser: + MockedUser.get.return_value = self.data + response = toggl.User.get() + self.assertTrue(response) def test_client_wrong_token(self): """ Should raise exception when wrong API token is provided. """ wrong_api_token = 'wrong token' - self.assertRaises(Exception, api.Client, wrong_api_token) + toggl = api.Client(wrong_api_token) + self.assertRaises(HTTPError, toggl.User.get) if __name__ == '__main__': From 069678780b240878ace57b1d7a765543b921d134 Mon Sep 17 00:00:00 2001 From: aarose Date: Tue, 11 Aug 2015 22:38:58 -0400 Subject: [PATCH 02/26] Fleshed out api more --- togglwrapper/api.py | 64 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/togglwrapper/api.py b/togglwrapper/api.py index a2123d6..393d729 100644 --- a/togglwrapper/api.py +++ b/togglwrapper/api.py @@ -26,6 +26,28 @@ def full_uri(self): def __init__(self, client): self.client = client + @return_json_or_raise_error + def get(self, object_id=None): + """ Get the array of objects, or a specific instance by ID. """ + uri = self.full_uri + if object_id is not None: + uri = '{uri}/{object_id}'.format(uri=uri, object_id=object_id) + return requests.get(uri, auth=self.client.auth) + + @return_json_or_raise_error + def create(self, data): + """ Create a new instance of the object type. """ + return requests.post(self.full_uri, data=data, auth=self.client.auth) + + @return_json_or_raise_error + def update(self, object_id, data): + uri = '{uri}/{object_id}'.format(self.full_uri, object_id) + return requests.put(uri, auth=self.client.auth) + + @return_json_or_raise_error + def delete(self, object_id): + return requests.delete(uri, auth=self.client.auth) + class User(TogglObject): uri = '/me' @@ -40,9 +62,51 @@ def update(self, data): pass +class Clients(TogglObject): + uri = '/clients' + + +class Workspaces(TogglObject): + uri = '/workspaces' + + +class Projects(TogglObject): + uri = '/projects' + + +class ProjectUsers(TogglObject): + uri = '/project_users' + + +class Tags(TogglObject): + uri = '/tags' + + +class Tasks(TogglObject): + uri = '/tasks' + + +class TimeEntries(TogglObject): + uri = '/time_entires' + + +class WorkspaceUsers(TogglObject): + uri = '/workspace_users' + + class Client(object): def __init__(self, api_token, base_url=BASE_URL, version=API_VERSION): self.api_url = '{base}/{version}'.format(base=base_url, version=version) self.auth = HTTPBasicAuth(api_token, 'api_token') self.User = User(self) + + def signup(self): + """ Signup a new user. """ + uri = '{}/signups'.format(self.api_url) + requests.post(uri, auth=self.auth) + + def reset_token(self): + """ Delete the current API Token and use a new token. """ + uri = '{}/reset_token'.format(self.api_url) + requests.post(uri, auth=self.auth) From 2fc445f3ea13e08a31c80fdf7d81b56661ab415e Mon Sep 17 00:00:00 2001 From: aarose Date: Thu, 13 Aug 2015 01:08:55 -0400 Subject: [PATCH 03/26] Fixed User get --- togglwrapper/api.py | 59 +++++++++++++++++++++++++++++-------------- togglwrapper/tests.py | 25 ++++++++++++------ 2 files changed, 57 insertions(+), 27 deletions(-) diff --git a/togglwrapper/api.py b/togglwrapper/api.py index 393d729..a642bd1 100644 --- a/togglwrapper/api.py +++ b/togglwrapper/api.py @@ -46,30 +46,14 @@ def update(self, object_id, data): @return_json_or_raise_error def delete(self, object_id): + uri = '{uri}/{object_id}'.format(self.full_uri, object_id) return requests.delete(uri, auth=self.client.auth) -class User(TogglObject): - uri = '/me' - - @return_json_or_raise_error - def get(self, related_data=False, since=None): - """ Get the user associated with the current API token. """ - return requests.get(self.full_uri, auth=self.client.auth) - - def update(self, data): - """ Update the fields. """ - pass - - class Clients(TogglObject): uri = '/clients' -class Workspaces(TogglObject): - uri = '/workspaces' - - class Projects(TogglObject): uri = '/projects' @@ -90,6 +74,34 @@ class TimeEntries(TogglObject): uri = '/time_entires' +class User(TogglObject): + uri = '/me' + + def _compile_query(self, related_data=False, since=None): + """ Returns the querystring. """ + query = '?with_related_data=true' if related_data else '' + if related_data and since is not None: + query += '&' + if since is not None: + query += '?since={}'.format(since) + return query + + @return_json_or_raise_error + def get(self, related_data=False, since=None): + """ Get the user associated with the current API token. """ + query = self._compile_query(related_data=related_data, since=since) + uri = '{uri}{query}'.format(uri=self.full_uri, query=query) + return requests.get(uri, auth=self.client.auth) + + def update(self, data): + """ Update the fields. """ + return requests.put(self.full_uri, data=data, auth=self.client.auth) + + +class Workspaces(TogglObject): + uri = '/workspaces' + + class WorkspaceUsers(TogglObject): uri = '/workspace_users' @@ -99,10 +111,19 @@ def __init__(self, api_token, base_url=BASE_URL, version=API_VERSION): self.api_url = '{base}/{version}'.format(base=base_url, version=version) self.auth = HTTPBasicAuth(api_token, 'api_token') + self.Clients = Clients(self) + self.User = User(self) + self.Projects = Projects(self) + self.ProjectUsers = ProjectUsers(self) + self.Tags = Tags(self) + self.Tasks = Tasks(self) + self.TimeEntries = TimeEntries(self) self.User = User(self) + self.Workspaces = Workspaces(self) + self.WorkspaceUsers = WorkspaceUsers(self) - def signup(self): - """ Signup a new user. """ + def signups(self): + """ Create a new user. """ uri = '{}/signups'.format(self.api_url) requests.post(uri, auth=self.auth) diff --git a/togglwrapper/tests.py b/togglwrapper/tests.py index 8ef3e5b..52a97b8 100644 --- a/togglwrapper/tests.py +++ b/togglwrapper/tests.py @@ -8,25 +8,34 @@ class TestAPIMethods(unittest.TestCase): + def setUp(self): - with open('json/user_get.json') as json_data: - self.data = json.load(json_data) - json_data.close() + self.api_token = 'fake_token_1' def test_client(self): """ Should successfully establish the client. """ - api_token = 'fake_token_1' - toggl = api.Client(api_token) + # Grab the JSON response to return + with open('json/user_get.json') as json_data: + data = json.load(json_data) + json_data.close() + toggl = api.Client(self.api_token) with patch.object(toggl, 'User') as MockedUser: - MockedUser.get.return_value = self.data + MockedUser.get.return_value = data response = toggl.User.get() self.assertTrue(response) + self.assertEqual(response['data']['api_token'], self.api_token) def test_client_wrong_token(self): """ Should raise exception when wrong API token is provided. """ - wrong_api_token = 'wrong token' - toggl = api.Client(wrong_api_token) + toggl = api.Client(self.api_token) self.assertRaises(HTTPError, toggl.User.get) + # Check that the error is a 403 error + + def test_update_user(self): + """ Should change a property of the User. """ + + def test_update_user_incorrect(self): + """ Should raise a 404 error. """ if __name__ == '__main__': From bf7125936041440dd86a72361f6bb46117979e0a Mon Sep 17 00:00:00 2001 From: aarose Date: Thu, 13 Aug 2015 15:00:42 -0400 Subject: [PATCH 04/26] Added generic method requests --- togglwrapper/api.py | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/togglwrapper/api.py b/togglwrapper/api.py index a642bd1..e769c70 100644 --- a/togglwrapper/api.py +++ b/togglwrapper/api.py @@ -1,3 +1,5 @@ +import json + import requests from requests.auth import HTTPBasicAuth @@ -122,12 +124,41 @@ def __init__(self, api_token, base_url=BASE_URL, version=API_VERSION): self.Workspaces = Workspaces(self) self.WorkspaceUsers = WorkspaceUsers(self) - def signups(self): - """ Create a new user. """ - uri = '{}/signups'.format(self.api_url) - requests.post(uri, auth=self.auth) + def signups(self, user_info): + """ + Create a new user. + + Args: + user_info (dict): Values for all the required and optional fields. + """ + return self.post('/signups', {'user': user_info}) def reset_token(self): """ Delete the current API Token and use a new token. """ - uri = '{}/reset_token'.format(self.api_url) - requests.post(uri, auth=self.auth) + return self.post('/reset_token') + + @return_json_or_raise_error + def get(self, uri): + """ GET to the given uri. """ + full_uri = '{base}{uri}'.format(base=self.api_url, uri=uri) + return requests.get(full_uri, auth=self.auth) + + @return_json_or_raise_error + def post(self, uri, data=None): + """ POST to the given uri with a data dict. """ + full_uri = '{base}{uri}'.format(base=self.api_url, uri=uri) + payload = json.dumps(data) if data is not None else None + return requests.post(full_uri, data=payload, auth=self.auth) + + @return_json_or_raise_error + def put(self, uri, data): + """ PUT to the given uri with a data dict. """ + full_uri = '{base}{uri}'.format(base=self.api_url, uri=uri) + payload = json.dumps(data) + return requests.put(full_uri, data=payload, auth=self.auth) + + @return_json_or_raise_error + def delete(self, uri): + """ DELETE to the given uri. """ + full_uri = '{base}{uri}'.format(self.api_url, uri) + return requests.delete(full_uri, auth=self.auth) From ad51c9649de003a1bb805d79ecb9f3a882f7550f Mon Sep 17 00:00:00 2001 From: aarose Date: Wed, 19 Aug 2015 15:35:56 -0400 Subject: [PATCH 05/26] Added json responses for toggl API --- togglwrapper/json/client_create.json | 7 ++ togglwrapper/json/client_get.json | 11 +++ togglwrapper/json/client_update.json | 9 ++ togglwrapper/json/clients_get.json | 15 +++ togglwrapper/json/dashboard.json | 91 +++++++++++++++++++ togglwrapper/json/project_create.json | 14 +++ togglwrapper/json/project_get.json | 14 +++ .../json/project_projectusers_get.json | 18 ++++ togglwrapper/json/project_tasks_get.json | 19 ++++ togglwrapper/json/project_update.json | 13 +++ togglwrapper/json/projectuser_create.json | 10 ++ togglwrapper/json/projectuser_update.json | 12 +++ .../json/projectusers_create_multiple.json | 26 ++++++ togglwrapper/json/projectusers_get.json | 10 ++ .../json/projectusers_update_multiple.json | 29 ++++++ togglwrapper/json/reset_token.json | 1 + togglwrapper/json/signups.json | 19 ++++ togglwrapper/json/tag_create.json | 7 ++ togglwrapper/json/tag_update.json | 7 ++ togglwrapper/json/task_create.json | 10 ++ togglwrapper/json/task_get.json | 10 ++ togglwrapper/json/task_update.json | 13 +++ togglwrapper/json/tasks_update_multiple.json | 24 +++++ .../json/time_entries_get_in_range.json | 24 +++++ .../json/time_entries_update_multiple.json | 29 ++++++ togglwrapper/json/time_entry_create.json | 13 +++ togglwrapper/json/time_entry_current.json | 12 +++ togglwrapper/json/time_entry_get.json | 15 +++ togglwrapper/json/time_entry_start.json | 13 +++ togglwrapper/json/time_entry_stop.json | 13 +++ togglwrapper/json/time_entry_update.json | 15 +++ .../json/user_get_with_related_data.json | 85 +++++++++++++++++ togglwrapper/json/user_update.json | 31 +++++++ togglwrapper/json/workspace_clients.json | 19 ++++ togglwrapper/json/workspace_get.json | 16 ++++ togglwrapper/json/workspace_invite.json | 12 +++ togglwrapper/json/workspace_projects.json | 21 +++++ togglwrapper/json/workspace_tags.json | 15 +++ togglwrapper/json/workspace_tasks.json | 29 ++++++ togglwrapper/json/workspace_update.json | 16 ++++ togglwrapper/json/workspace_users.json | 47 ++++++++++ .../json/workspace_workspaceusers.json | 22 +++++ togglwrapper/json/workspaces_get.json | 28 ++++++ togglwrapper/json/workspaceuser_update.json | 9 ++ 44 files changed, 873 insertions(+) create mode 100644 togglwrapper/json/client_create.json create mode 100644 togglwrapper/json/client_get.json create mode 100644 togglwrapper/json/client_update.json create mode 100644 togglwrapper/json/clients_get.json create mode 100644 togglwrapper/json/dashboard.json create mode 100644 togglwrapper/json/project_create.json create mode 100644 togglwrapper/json/project_get.json create mode 100644 togglwrapper/json/project_projectusers_get.json create mode 100644 togglwrapper/json/project_tasks_get.json create mode 100644 togglwrapper/json/project_update.json create mode 100644 togglwrapper/json/projectuser_create.json create mode 100644 togglwrapper/json/projectuser_update.json create mode 100644 togglwrapper/json/projectusers_create_multiple.json create mode 100644 togglwrapper/json/projectusers_get.json create mode 100644 togglwrapper/json/projectusers_update_multiple.json create mode 100644 togglwrapper/json/reset_token.json create mode 100644 togglwrapper/json/signups.json create mode 100644 togglwrapper/json/tag_create.json create mode 100644 togglwrapper/json/tag_update.json create mode 100644 togglwrapper/json/task_create.json create mode 100644 togglwrapper/json/task_get.json create mode 100644 togglwrapper/json/task_update.json create mode 100644 togglwrapper/json/tasks_update_multiple.json create mode 100644 togglwrapper/json/time_entries_get_in_range.json create mode 100644 togglwrapper/json/time_entries_update_multiple.json create mode 100644 togglwrapper/json/time_entry_create.json create mode 100644 togglwrapper/json/time_entry_current.json create mode 100644 togglwrapper/json/time_entry_get.json create mode 100644 togglwrapper/json/time_entry_start.json create mode 100644 togglwrapper/json/time_entry_stop.json create mode 100644 togglwrapper/json/time_entry_update.json create mode 100644 togglwrapper/json/user_get_with_related_data.json create mode 100644 togglwrapper/json/user_update.json create mode 100644 togglwrapper/json/workspace_clients.json create mode 100644 togglwrapper/json/workspace_get.json create mode 100644 togglwrapper/json/workspace_invite.json create mode 100644 togglwrapper/json/workspace_projects.json create mode 100644 togglwrapper/json/workspace_tags.json create mode 100644 togglwrapper/json/workspace_tasks.json create mode 100644 togglwrapper/json/workspace_update.json create mode 100644 togglwrapper/json/workspace_users.json create mode 100644 togglwrapper/json/workspace_workspaceusers.json create mode 100644 togglwrapper/json/workspaces_get.json create mode 100644 togglwrapper/json/workspaceuser_update.json diff --git a/togglwrapper/json/client_create.json b/togglwrapper/json/client_create.json new file mode 100644 index 0000000..a5b4efc --- /dev/null +++ b/togglwrapper/json/client_create.json @@ -0,0 +1,7 @@ +{"data": { + "id": 1239455, + "wid": 777, + "name": "Very Big Company", + "at": "2013-02-26T08:45:28+00:00" + } +} diff --git a/togglwrapper/json/client_get.json b/togglwrapper/json/client_get.json new file mode 100644 index 0000000..c80c0db --- /dev/null +++ b/togglwrapper/json/client_get.json @@ -0,0 +1,11 @@ +{ + "data": { + "id":1239455, + "wid":777, + "name":"Very Big Company", + "at":"2013-02-26T08:45:28+00:00", + "notes": "Contact: John Jacob Jingleheimer Schmidt", + "hrate": 12, + "cur": "AUD" + } +} diff --git a/togglwrapper/json/client_update.json b/togglwrapper/json/client_update.json new file mode 100644 index 0000000..ac322dc --- /dev/null +++ b/togglwrapper/json/client_update.json @@ -0,0 +1,9 @@ +{ + "data": { + "id":1239455, + "wid":777, + "name":"Very Big Company", + "notes":"something about the client", + "at":"2013-02-26T08:55:28+00:00" + } +} diff --git a/togglwrapper/json/clients_get.json b/togglwrapper/json/clients_get.json new file mode 100644 index 0000000..abfc49a --- /dev/null +++ b/togglwrapper/json/clients_get.json @@ -0,0 +1,15 @@ +[ + { + "id":1239455, + "wid":777, + "name":"Very Big Company", + "notes":"something about the client", + "at":"2013-02-26T08:55:28+00:00" + }, { + "id":1239456, + "wid":777, + "name":"Small Startup", + "notes":"Really cool people", + "at":"2013-03-26T08:55:28+00:00" + } +] diff --git a/togglwrapper/json/dashboard.json b/togglwrapper/json/dashboard.json new file mode 100644 index 0000000..513689f --- /dev/null +++ b/togglwrapper/json/dashboard.json @@ -0,0 +1,91 @@ +{ + "most_active_user":[ + { + "user_id":426060, + "duration":97652 + }, + { + "user_id":682638, + "duration":30600 + }, + { + "user_id":426054, + "duration":23400 + }, + { + "user_id":682650, + "duration":21600 + }, + { + "user_id":426055, + "duration":7506 + } + ], + "activity":[ + { + "user_id":6822650, + "project_id":34654059, + "duration":-1392018288, + "description":"new design" + }, + { + "user_id":4260544, + "project_id":30349753, + "duration":-1392019190, + "description":"backend" + }, + { + "user_id":426055, + "project_id":3689864, + "duration":-1392021417, + "description":"writing post" + }, + { + "user_id":6822650, + "project_id":36898655, + "duration":10800, + "description":"design", + "stop":"2014-02-14T14:30:00+00:00" + }, + { + "user_id":35224123, + "project_id":0, + "duration":65573, + "stop":"2014-02-13T08:12:06+00:00" + }, + { + "user_id":35224123, + "project_id":3689865, + "duration":3600, + "stop":"2014-02-10T15:30:00+00:00" + }, + { + "user_id":35224123, + "project_id":3535357, + "duration":10800, + "description":"backend", + "stop":"2014-02-10T14:30:00+00:00" + }, + { + "user_id":6822638, + "project_id":3534557, + "duration":12600, + "description":"call", + "stop":"2014-02-10T12:22:00+00:00" + }, + { + "user_id":35224123, + "project_id":303258, + "duration":5280, + "description":"research for presentation", + "stop":"2014-02-10T11:58:00+00:00" + }, + { + "user_id":35224123, + "project_id":346409, + "duration":7200, + "description":"Meeting", + "stop":"2014-02-10T10:00:00+00:00" + } + ] +} diff --git a/togglwrapper/json/project_create.json b/togglwrapper/json/project_create.json new file mode 100644 index 0000000..f9951cf --- /dev/null +++ b/togglwrapper/json/project_create.json @@ -0,0 +1,14 @@ +{ + "data": { + "id":193838628, + "wid":777, + "cid":123397, + "name":"An awesome project", + "billable":false, + "is_private":true, + "active":true, + "at":"2013-03-06T12:15:37+00:00", + "template_id":10237, + "color": "5" + } +} diff --git a/togglwrapper/json/project_get.json b/togglwrapper/json/project_get.json new file mode 100644 index 0000000..8c44d6a --- /dev/null +++ b/togglwrapper/json/project_get.json @@ -0,0 +1,14 @@ +{ + "data": { + "id":193838628, + "wid":777, + "cid":123397, + "name":"An awesome project", + "billable":false, + "is_private":true, + "active":true, + "at":"2013-03-06T12:15:37+00:00", + "template":true, + "color": "5" + } +} diff --git a/togglwrapper/json/project_projectusers_get.json b/togglwrapper/json/project_projectusers_get.json new file mode 100644 index 0000000..f3872cc --- /dev/null +++ b/togglwrapper/json/project_projectusers_get.json @@ -0,0 +1,18 @@ +[ + { + "id":4692190, + "pid":777, + "uid":123, + "wid":99, + "manager":true, + "rate":4 + }, + { + "id":4692193, + "pid":777, + "uid":125, + "wid":99, + "manager":false, + "rate":4 + } +] diff --git a/togglwrapper/json/project_tasks_get.json b/togglwrapper/json/project_tasks_get.json new file mode 100644 index 0000000..b637d5b --- /dev/null +++ b/togglwrapper/json/project_tasks_get.json @@ -0,0 +1,19 @@ +[ + { + "name":"A new task", + "id":1335076912, + "wid":888, + "pid":777, + "active":false, + "at":"2013-02-26T15:09:52+00:00", + "estimated_seconds":3600 + }, { + "name":"Another task", + "id":1335076911, + "uid": 12309, + "wid":888, + "pid":777, + "active":false, + "at":"2013-02-26T15:09:52+00:00" + } +] diff --git a/togglwrapper/json/project_update.json b/togglwrapper/json/project_update.json new file mode 100644 index 0000000..c257dbf --- /dev/null +++ b/togglwrapper/json/project_update.json @@ -0,0 +1,13 @@ +{ + "data": { + "id":193838628, + "wid":777, + "cid":123398, + "name":"Changed the name", + "billable":false, + "active":true, + "at":"2013-03-06T12:15:37+00:00", + "template":true, + "color":"6" + } +} diff --git a/togglwrapper/json/projectuser_create.json b/togglwrapper/json/projectuser_create.json new file mode 100644 index 0000000..0d2da87 --- /dev/null +++ b/togglwrapper/json/projectuser_create.json @@ -0,0 +1,10 @@ +{ + "data": { + "id":4692190, + "pid":777, + "uid":123, + "wid":99, + "manager":true, + "rate":4 + } +} diff --git a/togglwrapper/json/projectuser_update.json b/togglwrapper/json/projectuser_update.json new file mode 100644 index 0000000..97cbc63 --- /dev/null +++ b/togglwrapper/json/projectuser_update.json @@ -0,0 +1,12 @@ +{ + "data": { + "id":4692190, + "pid":777, + "uid":123, + "wid":99, + "manager":false, + "rate":15, + "fullname":"John Swift", + "at":"2013-03-05T09:21:44+00:00" + } +} diff --git a/togglwrapper/json/projectusers_create_multiple.json b/togglwrapper/json/projectusers_create_multiple.json new file mode 100644 index 0000000..ed792e2 --- /dev/null +++ b/togglwrapper/json/projectusers_create_multiple.json @@ -0,0 +1,26 @@ +{ + "data":[ + { + "id":4692190, + "pid":777, + "uid":1267998, + "wid":99, + "manager":true, + "rate":4, + },{ + "id":4692192, + "pid":777, + "uid":29624, + "wid":99, + "manager":true, + "rate":4, + },{ + "id":4692191, + "pid":777, + "uid":112047, + "wid":99, + "manager":true, + "rate":4, + } + ] +} diff --git a/togglwrapper/json/projectusers_get.json b/togglwrapper/json/projectusers_get.json new file mode 100644 index 0000000..0d2da87 --- /dev/null +++ b/togglwrapper/json/projectusers_get.json @@ -0,0 +1,10 @@ +{ + "data": { + "id":4692190, + "pid":777, + "uid":123, + "wid":99, + "manager":true, + "rate":4 + } +} diff --git a/togglwrapper/json/projectusers_update_multiple.json b/togglwrapper/json/projectusers_update_multiple.json new file mode 100644 index 0000000..cea6fc2 --- /dev/null +++ b/togglwrapper/json/projectusers_update_multiple.json @@ -0,0 +1,29 @@ +{ + "data":[ + { + "id":4692190, + "pid":777, + "uid":1267998, + "wid":99, + "manager":false, + "rate":15, + "at":"2013-03-05T09:20:58+00:00" + },{ + "id":4692192, + "pid":777, + "uid":29624, + "wid":99, + "manager":false, + "rate":15, + "at":"2013-03-05T09:20:58+00:00" + },{ + "id":4692191, + "pid":777, + "uid":112047, + "wid":99, + "manager":false, + "rate":15, + "at":"2013-03-05T09:20:58+00:00" + } + ] +} diff --git a/togglwrapper/json/reset_token.json b/togglwrapper/json/reset_token.json new file mode 100644 index 0000000..6819458 --- /dev/null +++ b/togglwrapper/json/reset_token.json @@ -0,0 +1 @@ +"a0123123b8e43343d614553f95f9192ab9c1" diff --git a/togglwrapper/json/signups.json b/togglwrapper/json/signups.json new file mode 100644 index 0000000..2901d63 --- /dev/null +++ b/togglwrapper/json/signups.json @@ -0,0 +1,19 @@ +{ + "data":{ + "id":599978901, + "api_token":"808lolae4eab897cce9729a53642124effe", + "default_wid":983493, + "email":"test.user@toggl.com", + "fullname":"Test User", + "jquery_timeofday_format":"", + "jquery_date_format":"", + "timeofday_format":"", + "date_format":"", + "store_start_and_stop_time":false, + "beginning_of_week":0, + "sidebar_piechart":false, + "timeline_experiment":false, + "new_blog_post":{}, + "invitation":{} + } +} diff --git a/togglwrapper/json/tag_create.json b/togglwrapper/json/tag_create.json new file mode 100644 index 0000000..a0cbc84 --- /dev/null +++ b/togglwrapper/json/tag_create.json @@ -0,0 +1,7 @@ +{ + "data": { + "id":1239455, + "wid":777, + "name":"billed" + } +} diff --git a/togglwrapper/json/tag_update.json b/togglwrapper/json/tag_update.json new file mode 100644 index 0000000..4687967 --- /dev/null +++ b/togglwrapper/json/tag_update.json @@ -0,0 +1,7 @@ +{ + "data": { + "id":1239455, + "wid":777, + "name":"not billed" + } +} diff --git a/togglwrapper/json/task_create.json b/togglwrapper/json/task_create.json new file mode 100644 index 0000000..7694ba6 --- /dev/null +++ b/togglwrapper/json/task_create.json @@ -0,0 +1,10 @@ +{ + "data": { + "name":"A new task", + "id":1335076912, + "wid":888, + "pid":777, + "active":true, + "estimated_seconds":0 + } +} diff --git a/togglwrapper/json/task_get.json b/togglwrapper/json/task_get.json new file mode 100644 index 0000000..7694ba6 --- /dev/null +++ b/togglwrapper/json/task_get.json @@ -0,0 +1,10 @@ +{ + "data": { + "name":"A new task", + "id":1335076912, + "wid":888, + "pid":777, + "active":true, + "estimated_seconds":0 + } +} diff --git a/togglwrapper/json/task_update.json b/togglwrapper/json/task_update.json new file mode 100644 index 0000000..086e724 --- /dev/null +++ b/togglwrapper/json/task_update.json @@ -0,0 +1,13 @@ +{ + "data": { + "name":"A new task", + "id":1335076912, + "wid":888, + "pid":777, + "active":false, + "at":"2013-02-26T15:09:52+00:00", + "estimated_seconds":3600, + "uname": "John Swift", + "done_seconds": 1200 + } +} diff --git a/togglwrapper/json/tasks_update_multiple.json b/togglwrapper/json/tasks_update_multiple.json new file mode 100644 index 0000000..4bd2fd9 --- /dev/null +++ b/togglwrapper/json/tasks_update_multiple.json @@ -0,0 +1,24 @@ +{ + "data": [ + { + "name":"A new task", + "id":1335076912, + "wid":888, + "pid":777, + "active":false, + "at":"2013-02-26T15:09:52+00:00", + "estimated_seconds":3600, + "uname": "John Swift", + "done_seconds": 1200 + }, { + "name":"Another task", + "id":1335076911, + "wid":888, + "pid":777, + "active":false, + "at":"2013-02-26T15:09:52+00:00", + "estimated_seconds":3600, + "done_seconds": 10 + } + ] +} diff --git a/togglwrapper/json/time_entries_get_in_range.json b/togglwrapper/json/time_entries_get_in_range.json new file mode 100644 index 0000000..dfc046c --- /dev/null +++ b/togglwrapper/json/time_entries_get_in_range.json @@ -0,0 +1,24 @@ +[ + { + "id":436691234, + "wid":777, + "pid":123, + "billable":true, + "start":"2013-03-11T11:36:00+00:00", + "stop":"2013-03-11T15:36:00+00:00", + "duration":14400, + "description":"Meeting with the client", + "tags":[""], + "at":"2013-03-11T15:36:58+00:00" + },{ + "id":436776436, + "wid":777, + "billable":false, + "start":"2013-03-12T10:32:43+00:00", + "stop":"2013-03-12T14:32:43+00:00", + "duration":18400, + "description":"important work", + "tags":[""], + "at":"2013-03-12T14:32:43+00:00" + } +] diff --git a/togglwrapper/json/time_entries_update_multiple.json b/togglwrapper/json/time_entries_update_multiple.json new file mode 100644 index 0000000..b2f67eb --- /dev/null +++ b/togglwrapper/json/time_entries_update_multiple.json @@ -0,0 +1,29 @@ +{ "data":[ + { + "id":436694100, + "guid":"dfc88541-f3b0-bffe-fa2e-46a09fa97664", + "wid":777, + "pid":20123718, + "billable":true, + "start":"2013-08-01T10:46:00", + "stop":"2013-08-01T11:46:02", + "duration":3602, + "description":"Development", + "tags":["billed","poductive","overhours"], + "duronly":false, + "at":"2013-08-01T12:04:57" + },{ + "id":436694101, + "guid":"ce3c2409-e624-64e2-6742-c623ff284091", + "wid":777, + "billable":false, + "start":"2013-08-01T11:11:00", + "stop":"2013-08-01T11:46:04", + "duration":2104, + "description":"Meeting with the client", + "tags":["billed","poductive","holiday"], + "duronly":false, + "at":"2013-08-01T11:46:08" + } + ] +} diff --git a/togglwrapper/json/time_entry_create.json b/togglwrapper/json/time_entry_create.json new file mode 100644 index 0000000..99abe80 --- /dev/null +++ b/togglwrapper/json/time_entry_create.json @@ -0,0 +1,13 @@ +{ + "data": + { + "id":436694100, + "pid":123, + "wid":777, + "billable":false, + "start":"2013-03-05T07:58:58.000Z", + "duration":1200, + "description":"Meeting with possible clients", + "tags":["billed"] + } +} diff --git a/togglwrapper/json/time_entry_current.json b/togglwrapper/json/time_entry_current.json new file mode 100644 index 0000000..38f68af --- /dev/null +++ b/togglwrapper/json/time_entry_current.json @@ -0,0 +1,12 @@ +{ + "data":{ + "id":436694100, + "wid":777, + "pid":193791, + "billable":false, + "start":"2014-01-30T09:08:04+00:00", + "duration":-1391072884, + "description":"Running time entry", + "at":"2014-01-30T09:08:12+00:00" + } +} diff --git a/togglwrapper/json/time_entry_get.json b/togglwrapper/json/time_entry_get.json new file mode 100644 index 0000000..2a39979 --- /dev/null +++ b/togglwrapper/json/time_entry_get.json @@ -0,0 +1,15 @@ +{ + "data":{ + "id":436694100, + "wid":777, + "pid":193791, + "tid":13350500, + "billable":false, + "start":"2013-02-27T01:24:00+00:00", + "stop":"2013-02-27T07:24:00+00:00", + "duration":21600, + "description":"Some serious work", + "tags":["billed"], + "at":"2013-02-27T13:49:18+00:00" + } +} diff --git a/togglwrapper/json/time_entry_start.json b/togglwrapper/json/time_entry_start.json new file mode 100644 index 0000000..ed5f984 --- /dev/null +++ b/togglwrapper/json/time_entry_start.json @@ -0,0 +1,13 @@ +{ + "data": + { + "id":436694100, + "pid":123, + "wid":777, + "billable":false, + "start":"2013-03-05T07:58:58.000Z", + "duration":-1362470338, + "description":"Meeting with possible clients", + "tags":["billed"] + } +} diff --git a/togglwrapper/json/time_entry_stop.json b/togglwrapper/json/time_entry_stop.json new file mode 100644 index 0000000..a5c86ed --- /dev/null +++ b/togglwrapper/json/time_entry_stop.json @@ -0,0 +1,13 @@ +{ + "data": + { + "id":436694100, + "pid":123, + "wid":777, + "billable":false, + "start":"2013-03-05T07:58:58.000Z", + "duration":60, + "description":"Meeting with possible clients", + "tags":["billed"] + } +} diff --git a/togglwrapper/json/time_entry_update.json b/togglwrapper/json/time_entry_update.json new file mode 100644 index 0000000..4d4c21b --- /dev/null +++ b/togglwrapper/json/time_entry_update.json @@ -0,0 +1,15 @@ +{ + "data": + { + "id":436694100, + "pid":123, + "wid":777, + "billable":false, + "start":"2013-03-05T07:58:58.000Z", + "stop":"2013-03-05T08:58:58.000Z", + "duration":1240, + "description":"Meeting with possible clients", + "billable": true, + "at": "2013-03-05T12:34:50+00:00" + } +} diff --git a/togglwrapper/json/user_get_with_related_data.json b/togglwrapper/json/user_get_with_related_data.json new file mode 100644 index 0000000..dc1a1e9 --- /dev/null +++ b/togglwrapper/json/user_get_with_related_data.json @@ -0,0 +1,85 @@ +{ + "since":1362575771, + "data": { + "id":9000, + "api_token":"1971800d4d82861d8f2c1651fea4d212", + "default_wid":777, + "email":"johnt@swift.com", + "fullname":"John Swift", + "jquery_timeofday_format":"h:i A", + "jquery_date_format":"m/d/Y", + "timeofday_format":"h:mm A", + "date_format":"MM/DD/YYYY", + "store_start_and_stop_time":true, + "beginning_of_week":0, + "language":"en_US", + "image_url":"https://www.toggl.com/system/avatars/9000/small/open-uri20121116-2767-b1qr8l.png", + "sidebar_piechart":false, + "at":"2013-03-06T12:18:42+00:00", + "retention":9, + "record_timeline":true, + "render_timeline":true, + "timeline_enabled":true, + "timeline_experiment":true, + "new_blog_post": + { + "title":"Increasing perceived performance with _.throttle()", + "url":"http://blog.toggl.com/2013/02/increasing-perceived-performance-with-_throttle/?utm_source=rss&utm_medium=rss&utm_campaign=increasing-perceived-performance-with-_throttle" + }, + "time_entries":[ + { + "id":435559433, + "wid":777, + "billable":false, + "start":"2013-03-06T10:08:23+00:00", + "stop":"2013-03-06T14:08:23+00:00", + "duration":14400, + "description":"Best work so far", + "tags":[""], + "at":"2013-03-06T14:08:23+00:00" + } + ], + "projects":[ + { + "id":1230994, + "wid":777, + "name":"Important project", + "billable":false, + "active":false, + "at":"2013-03-06T09:13:31+00:00" + "color":"5" + } + ], + "tags":[ + { + "id":159637, + "wid":188309, + "name":"billable", + "at":"2013-02-21T14:57:46+00:00" + },{ + "id":159654, + "wid":188309, + "name":"important", + "at":"2013-02-22T14:06:17+00:00" + } + ], + "workspaces":[ + { + "id":777, + "name": + "John's WS", + "premium":true, + "at":"2013-03-06T09:00:30+00:00" + } + ], + "clients":[ + { + "id":923476, + "wid":777, + "name":"Best client", + "at":"2013-03-06T09:00:30+00:00" + } + ] + + } +} diff --git a/togglwrapper/json/user_update.json b/togglwrapper/json/user_update.json new file mode 100644 index 0000000..f3fd162 --- /dev/null +++ b/togglwrapper/json/user_update.json @@ -0,0 +1,31 @@ +{ + "data":{ + "id":123123123, + "api_token":"1971800d4d82861d8f2c1651fea4d212", + "default_wid":777, + "email":"john@toggl.com", + "fullname":"John Smith", + "jquery_timeofday_format":"h:i A", + "jquery_date_format":"d.m.Y", + "timeofday_format":"h:mm A", + "date_format":"DD.MM.YYYY", + "store_start_and_stop_time":true, + "beginning_of_week":1, + "language":"en_US", + "image_url":"https://www.toggl.com/system/avatars/9000/small/open-uri20121116-2767-b1qr8l.png", + "sidebar_piechart":false, + "at":"2013-08-12T11:55:58+03:00", + "retention":9, + "record_timeline":true, + "render_timeline":true, + "timeline_enabled":true, + "timeline_experiment":true, + "new_blog_post":{}, + "timezone":"Europe/Tallinn", + "openid_enabled":true, + "send_product_emails":true, + "send_weekly_report":true, + "send_timer_notifications":true, + "invitation":{} + } +} diff --git a/togglwrapper/json/workspace_clients.json b/togglwrapper/json/workspace_clients.json new file mode 100644 index 0000000..c0cdf73 --- /dev/null +++ b/togglwrapper/json/workspace_clients.json @@ -0,0 +1,19 @@ +[ + { + "id":123, + "wid":777, + "name":"Rising Start-Up", + "at":"2013-03-06T09:06:13+00:00", + "notes":"Arrange a discount for them", + "hrate":2, + "cur":"USD" + },{ + "id":987, + "wid":777, + "name":"Big Company Inc", + "at":"2013-03-06T09:05:40+00:00", + "notes":"We had some lovely projects with them", + "hrate":10, + "cur":"EUR" + } +] diff --git a/togglwrapper/json/workspace_get.json b/togglwrapper/json/workspace_get.json new file mode 100644 index 0000000..5bdac60 --- /dev/null +++ b/togglwrapper/json/workspace_get.json @@ -0,0 +1,16 @@ +{ + "data": { + "id":3134975, + "name":"John's personal ws", + "premium":true, + "admin":true, + "default_hourly_rate":150, + "default_currency":"USD", + "only_admins_may_create_projects":false, + "only_admins_see_billable_rates":false, + "rounding":1, + "rounding_minutes":15, + "at":"2013-08-28T16:22:21+03:00", + "logo_url":"my_logo.png" + } +} diff --git a/togglwrapper/json/workspace_invite.json b/togglwrapper/json/workspace_invite.json new file mode 100644 index 0000000..109edce --- /dev/null +++ b/togglwrapper/json/workspace_invite.json @@ -0,0 +1,12 @@ +{ + "data":{[ + "id":3511220, + "uid":35934278, + "wid":777, + "admin":false, + "active":false, + "email":"jane.swift@toggl.com", + "invite_url":"https://toggl.com/user/accept_invitation?code=ec2876e421234dfasd0fa1c55370d3940" + ]}, + "notifications":["To add more users you have to Upgrade"] +} diff --git a/togglwrapper/json/workspace_projects.json b/togglwrapper/json/workspace_projects.json new file mode 100644 index 0000000..5fe114b --- /dev/null +++ b/togglwrapper/json/workspace_projects.json @@ -0,0 +1,21 @@ +[ + { + "id":909, + "wid":777, + "cid":987, + "name":"Very lucrative project", + "billable":false, + "is_private":true, + "active":true, + "at":"2013-03-06T09:15:18+00:00" + },{ + "id":32123, + "wid":777, + "cid":123, + "name":"Factory server infrastructure", + "billable":true, + "is_private":true, + "active":true, + "at":"2013-03-06T09:16:06+00:00" + } +] diff --git a/togglwrapper/json/workspace_tags.json b/togglwrapper/json/workspace_tags.json new file mode 100644 index 0000000..81109e9 --- /dev/null +++ b/togglwrapper/json/workspace_tags.json @@ -0,0 +1,15 @@ +[ + { + "id":151285, + "wid":777, + "name":"Billed" + },{ + "id":1596623, + "wid":777, + "name":"Invoiced" + },{ + "id":159643, + "wid":777, + "name":"Discarded" + } +] diff --git a/togglwrapper/json/workspace_tasks.json b/togglwrapper/json/workspace_tasks.json new file mode 100644 index 0000000..50c79df --- /dev/null +++ b/togglwrapper/json/workspace_tasks.json @@ -0,0 +1,29 @@ +[ + { + "name":"SWOT", + "id":13512097, + "wid":777, + "pid":32123, + "uid":123123, + "active":true, + "at":"2013-03-06T09:15:51+00:00", + "estimated_seconds":7200 + },{ + "name":"development", + "id":133504498, + "wid":777, + "pid":32123, + "active":true, + "at":"2013-03-06T09:15:59+00:00", + "estimated_seconds":0 + },{ + "name":"analyze SEO", + "id":1335112300, + "wid":777, + "pid":909, + "active":true, + "at":"2013-03-06T09:18:57+00:00", + "estimated_seconds":21600 + } + +] diff --git a/togglwrapper/json/workspace_update.json b/togglwrapper/json/workspace_update.json new file mode 100644 index 0000000..9b57ffe --- /dev/null +++ b/togglwrapper/json/workspace_update.json @@ -0,0 +1,16 @@ +{ + "data": { + "id":3134975, + "name":"John's ws", + "premium":true, + "admin":true, + "default_hourly_rate":50, + "default_currency":"USD", + "only_admins_may_create_projects":false, + "only_admins_see_billable_rates":true, + "rounding":1, + "rounding_minutes":60, + "at":"2013-08-28T16:22:21+03:00", + "logo_url":"my_logo.png" + } +} diff --git a/togglwrapper/json/workspace_users.json b/togglwrapper/json/workspace_users.json new file mode 100644 index 0000000..0ba6b85 --- /dev/null +++ b/togglwrapper/json/workspace_users.json @@ -0,0 +1,47 @@ +[ + { + "id":123123, + "default_wid":777, + "email":"john@swift.com", + "fullname":"John Swift", + "jquery_timeofday_format":"h:i A", + "jquery_date_format":"m/d/Y", + "timeofday_format":"h:mm A", + "date_format":"MM/DD/YYYY", + "store_start_and_stop_time":true, + "beginning_of_week":0, + "language":"en_US", + "image_url":"https://www.toggl.com/system/avatars/123123/small/open-uri20121116-2767-b1qr8l.png", + "sidebar_piechart":false, + "at":"2013-03-06T08:57:12+00:00", + "retention":9, + "record_timeline":true, + "render_timeline":true, + "timeline_enabled":true, + "timeline_experiment":true, + "new_blog_post":{}, + "should_upgrade":true, + "invitation":{} + },{ + "id":321321, + "email":"Happy@worker.com", + "fullname":"Happy Worker", + "jquery_timeofday_format":"h:i A", + "jquery_date_format":"m/d/Y", + "timeofday_format":"h:mm A", + "date_format":"MM/DD/YYYY", + "store_start_and_stop_time":true, + "beginning_of_week":1, + "language":"en_US", + "image_url":"https://www.toggl.com/images/profile.png", + "sidebar_piechart":false, + "at":"2013-03-06T08:46:07+00:00", + "created_at":"2013-03-06T07:52:03+00:00", + "retention":9, + "render_timeline":true, + "timeline_experiment":true, + "new_blog_post":{}, + "should_upgrade":true, + "invitation":{} + } +] diff --git a/togglwrapper/json/workspace_workspaceusers.json b/togglwrapper/json/workspace_workspaceusers.json new file mode 100644 index 0000000..cc4b92c --- /dev/null +++ b/togglwrapper/json/workspace_workspaceusers.json @@ -0,0 +1,22 @@ +[ + { + "id":3123855, + "uid":35224123, + "wid":777, + "admin":false, + "active":false, + "email":"John@toggl.com", + "at":"2013-05-17T16:50:36+03:00", + "name":"John Swift", + "invite_url":"https://toggl.com/user/accept_invitation?code=fb3ad3db5dasd123c2b529e3a519826" + },{ + "id":200066, + "uid":21961, + "wid":777, + "admin":true, + "active":true, + "email":"Jane@toggl.com", + "at":"2012-04-11T22:59:33+03:00", + "name":"Jane" + } +] diff --git a/togglwrapper/json/workspaces_get.json b/togglwrapper/json/workspaces_get.json new file mode 100644 index 0000000..75359ab --- /dev/null +++ b/togglwrapper/json/workspaces_get.json @@ -0,0 +1,28 @@ +[ + { + "id":3134975, + "name":"John's personal ws", + "premium":true, + "admin":true, + "default_hourly_rate":50, + "default_currency":"USD", + "only_admins_may_create_projects":false, + "only_admins_see_billable_rates":true, + "rounding":1, + "rounding_minutes":15, + "at":"2013-08-28T16:22:21+00:00", + "logo_url":"my_logo.png" + },{ + "id":777, + "name":"My Company Inc", + "premium":true, + "admin":true, + "default_hourly_rate":40, + "default_currency":"EUR", + "only_admins_may_create_projects":false, + "only_admins_see_billable_rates":true, + "rounding":1, + "rounding_minutes":15, + "at":"2013-08-28T16:22:21+00:00" + } +] diff --git a/togglwrapper/json/workspaceuser_update.json b/togglwrapper/json/workspaceuser_update.json new file mode 100644 index 0000000..810e8d7 --- /dev/null +++ b/togglwrapper/json/workspaceuser_update.json @@ -0,0 +1,9 @@ +{ + "data": { + "id":19012628, + "uid":1625, + "wid":777, + "admin":false, + "active":true + } +} From e8da623dbc4b6de17fc3f281b5ce79c6112a15db Mon Sep 17 00:00:00 2001 From: aarose Date: Wed, 19 Aug 2015 15:46:05 -0400 Subject: [PATCH 06/26] Added AuthError for 403 status codes --- togglwrapper/api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/togglwrapper/api.py b/togglwrapper/api.py index e769c70..c6483f7 100644 --- a/togglwrapper/api.py +++ b/togglwrapper/api.py @@ -3,6 +3,8 @@ import requests from requests.auth import HTTPBasicAuth +from errors import AuthError + BASE_URL = 'https://www.toggl.com/api' API_VERSION = 'v8' @@ -16,6 +18,8 @@ def inner(*args, **kwargs): return response.json() except ValueError: # JSON couldn't be decoded, raise status error + if response.status_code == 403: + raise AuthError('Incorrect API token.') response.raise_for_status() return inner From 633e8fa7819d366daeb5ba0e81fb5722e963b007 Mon Sep 17 00:00:00 2001 From: aarose Date: Wed, 19 Aug 2015 15:46:42 -0400 Subject: [PATCH 07/26] Added forgotten errors file --- togglwrapper/errors.py | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 togglwrapper/errors.py diff --git a/togglwrapper/errors.py b/togglwrapper/errors.py new file mode 100644 index 0000000..b40d353 --- /dev/null +++ b/togglwrapper/errors.py @@ -0,0 +1,2 @@ +class AuthError(Exception): + """ Raised when authentication fails. """ From 021551b5a1db0f47a00a5114ecf423c59031f7ad Mon Sep 17 00:00:00 2001 From: aarose Date: Wed, 19 Aug 2015 15:49:17 -0400 Subject: [PATCH 08/26] Rename Client to Toggl to avoid confusion with Clients objects --- togglwrapper/api.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/togglwrapper/api.py b/togglwrapper/api.py index c6483f7..3c3e294 100644 --- a/togglwrapper/api.py +++ b/togglwrapper/api.py @@ -25,12 +25,13 @@ def inner(*args, **kwargs): class TogglObject(object): + @property def full_uri(self): return '{api}{uri}'.format(api=self.client.api_url, uri=self.uri) - def __init__(self, client): - self.client = client + def __init__(self, toggl): + self.toggl = toggl @return_json_or_raise_error def get(self, object_id=None): @@ -112,7 +113,7 @@ class WorkspaceUsers(TogglObject): uri = '/workspace_users' -class Client(object): +class Toggl(object): def __init__(self, api_token, base_url=BASE_URL, version=API_VERSION): self.api_url = '{base}/{version}'.format(base=base_url, version=version) From e4853384ac33809a5b64222cd311620814372631 Mon Sep 17 00:00:00 2001 From: aarose Date: Wed, 19 Aug 2015 15:50:03 -0400 Subject: [PATCH 09/26] Added params to general gets --- togglwrapper/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/togglwrapper/api.py b/togglwrapper/api.py index 3c3e294..f93fca4 100644 --- a/togglwrapper/api.py +++ b/togglwrapper/api.py @@ -143,10 +143,10 @@ def reset_token(self): return self.post('/reset_token') @return_json_or_raise_error - def get(self, uri): + def get(self, uri, params=None): """ GET to the given uri. """ full_uri = '{base}{uri}'.format(base=self.api_url, uri=uri) - return requests.get(full_uri, auth=self.auth) + return requests.get(full_uri, params=params, auth=self.auth) @return_json_or_raise_error def post(self, uri, data=None): From ed7bddbec4bdb6e8c0ea621b3c0f1d582f0e1261 Mon Sep 17 00:00:00 2001 From: aarose Date: Wed, 19 Aug 2015 15:57:29 -0400 Subject: [PATCH 10/26] Switched to using mixin/classes to choose which standard methods are available (get, create, update, delete). --- togglwrapper/api.py | 130 ++++++++++++++++++++++++++++++++------------ 1 file changed, 94 insertions(+), 36 deletions(-) diff --git a/togglwrapper/api.py b/togglwrapper/api.py index f93fca4..7f6f5aa 100644 --- a/togglwrapper/api.py +++ b/togglwrapper/api.py @@ -33,47 +33,98 @@ def full_uri(self): def __init__(self, toggl): self.toggl = toggl - @return_json_or_raise_error - def get(self, object_id=None): + +class Get(object): + + def _compile_uri(self, id=None): + uri = self.uri + if id is not None: + uri += '/%s' % id + return uri + + def get_child_objects(self, parent_id, child_uri, params=None): + """ + Get the Objects that belong to the parent Object with the given ID. + + Args: + parent_id (int): The ID of the parent Object. + child_uri (str): The uri of the child Object. e.g. If we wanted + the Clients of a Workspace, where the Workspace is the parent + object, the child-uri is '/clients'. + """ + uri = self._compile_uri(parent_id) + child_uri + return self.toggl.get(uri, params=params) + + def get(self, id=None, params=None): """ Get the array of objects, or a specific instance by ID. """ - uri = self.full_uri - if object_id is not None: - uri = '{uri}/{object_id}'.format(uri=uri, object_id=object_id) - return requests.get(uri, auth=self.client.auth) + return self.toggl.get(self._compile_uri(id), params=params) - @return_json_or_raise_error + +class Create(object): def create(self, data): """ Create a new instance of the object type. """ - return requests.post(self.full_uri, data=data, auth=self.client.auth) + return self.toggl.post(self.uri, data) - @return_json_or_raise_error - def update(self, object_id, data): - uri = '{uri}/{object_id}'.format(self.full_uri, object_id) - return requests.put(uri, auth=self.client.auth) - @return_json_or_raise_error - def delete(self, object_id): - uri = '{uri}/{object_id}'.format(self.full_uri, object_id) - return requests.delete(uri, auth=self.client.auth) +class Update(object): + def update(self, id=None, ids=None, data=None): + """ Update a specific instance by ID, or update multiple instances. """ + if not any(id, ids) or (id and ids): + raise Exception('Must provide either an ID or an iterable of IDs.') + if id is not None: + uri = '{uri}/{id}'.format(self.uri, id) + else: + uri = '{uri}/{ids}'.format(uri=self.uri, ids=','.join(ids)) + return self.toggl.put(uri, data) -class Clients(TogglObject): +class Delete(object): + def delete(self, id): + """ Delete a specific instance by ID. """ + uri = '{uri}/{id}'.format(self.uri, id) + return self.toggl.delete(uri) + + +class Clients(TogglObject, Get, Create, Update, Delete): uri = '/clients' + def get_projects(self, client_id, active=True): + """ + Get the projects associated with the Client with the given ID. + + Args: + client_id (int): The ID of the client. + active (bool or string, optional): Must be either True, False, or + the string 'both'. Defaults to True. + """ + cond1 = (active is True) + cond2 = (active is False) + cond3 = (active is 'both') + if not any(cond1, cond2, cond3): + raise Exception("The 'active' param must be either True, False,", + "or 'both'.") + params = {'active': active} + return self.get_child_objects(client_id, '/projects', params=params) + + def get(self, workspace_id): + """ Get the Dashboard for the Workspace with the given ID. """ + return super(Dashboard, self).get(id=workspace_id) -class Projects(TogglObject): + +class Projects(TogglObject, Get, Create, Update, Delete): uri = '/projects' -class ProjectUsers(TogglObject): +class ProjectUsers(TogglObject, Create, Update, Delete): uri = '/project_users' -class Tags(TogglObject): + +class Tags(TogglObject, Create, Update, Delete): uri = '/tags' -class Tasks(TogglObject): +class Tasks(TogglObject, Get, Create, Update, Delete): uri = '/tasks' @@ -81,19 +132,26 @@ class TimeEntries(TogglObject): uri = '/time_entires' -class User(TogglObject): - uri = '/me' +class TimeEntries(TogglObject, Get, Create, Update, Delete): + uri = '/time_entries' + + def get(self, id=None, start_date=None, end_date=None): + """ Get the time entry. """ + params = {'start_date': start_date, 'end_date': end_date} + return super(TimeEntries, self).get(id=id, params=params) + + def start(self, data): + """ Start a new time entry. """ + uri = self.uri + '/start' + return self.toggl.post(uri, data) + + def stop(self, time_entry_id): + """ Stop the time entry with the given ID. """ + uri = self.uri + '/{time_entry_id}' + return self.toggl.post(uri.format(time_entry_id=time_entry_id)) + - def _compile_query(self, related_data=False, since=None): - """ Returns the querystring. """ - query = '?with_related_data=true' if related_data else '' - if related_data and since is not None: - query += '&' - if since is not None: - query += '?since={}'.format(since) - return query - @return_json_or_raise_error def get(self, related_data=False, since=None): """ Get the user associated with the current API token. """ query = self._compile_query(related_data=related_data, since=since) @@ -101,15 +159,15 @@ def get(self, related_data=False, since=None): return requests.get(uri, auth=self.client.auth) def update(self, data): - """ Update the fields. """ - return requests.put(self.full_uri, data=data, auth=self.client.auth) + """ Update the user associated with the api token. """ + return self.toggl.put(self.uri, data) -class Workspaces(TogglObject): +class Workspaces(TogglObject, Get, Update): uri = '/workspaces' -class WorkspaceUsers(TogglObject): +class WorkspaceUsers(TogglObject, Update, Delete): uri = '/workspace_users' From 76cb177a588abdfcfc9c52978d65a2e5971b7be7 Mon Sep 17 00:00:00 2001 From: aarose Date: Wed, 19 Aug 2015 15:58:04 -0400 Subject: [PATCH 11/26] Fleshed out api more --- togglwrapper/api.py | 62 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/togglwrapper/api.py b/togglwrapper/api.py index 7f6f5aa..18d744e 100644 --- a/togglwrapper/api.py +++ b/togglwrapper/api.py @@ -106,6 +106,10 @@ def get_projects(self, client_id, active=True): params = {'active': active} return self.get_child_objects(client_id, '/projects', params=params) + +class Dashboard(TogglObject, Get): + uri = 'dashboard' + def get(self, workspace_id): """ Get the Dashboard for the Workspace with the given ID. """ return super(Dashboard, self).get(id=workspace_id) @@ -114,10 +118,25 @@ def get(self, workspace_id): class Projects(TogglObject, Get, Create, Update, Delete): uri = '/projects' + def get(self, project_id): + """ Get the Project with the given ID. """ + return super(Projects, self).get(id=project_id) + + def get_project_users(self, project_id): + """ Get the ProjectUsers for the Project with the given ID. """ + return self.get_child_objects(project_id, '/project_users') + + def get_tasks(self, project_id): + """ Get the Tasks for the Project with the given ID. """ + return self.get_child_objects(project_id, '/tasks') + class ProjectUsers(TogglObject, Create, Update, Delete): uri = '/project_users' + def get_for_project(self, project_id): + """ Get the ProjectUsers for the Project with the given ID. """ + return self.toggl.Projects.get_project_users(project_id) class Tags(TogglObject, Create, Update, Delete): @@ -127,9 +146,12 @@ class Tags(TogglObject, Create, Update, Delete): class Tasks(TogglObject, Get, Create, Update, Delete): uri = '/tasks' + def get(self, tag_id): + return super(Projects, self).get(id=tag_id) -class TimeEntries(TogglObject): - uri = '/time_entires' + def get_for_project(self, project_id): + """ Get the Tasks for the Project with the given ID. """ + return self.toggl.Projects.get_tasks(project_id) class TimeEntries(TogglObject, Get, Create, Update, Delete): @@ -151,12 +173,13 @@ def stop(self, time_entry_id): return self.toggl.post(uri.format(time_entry_id=time_entry_id)) +class User(TogglObject, Get): + uri = '/me' def get(self, related_data=False, since=None): """ Get the user associated with the current API token. """ - query = self._compile_query(related_data=related_data, since=since) - uri = '{uri}{query}'.format(uri=self.full_uri, query=query) - return requests.get(uri, auth=self.client.auth) + params = {'related_data': related_data, 'since': since} + return super(User, self).get(params=params) def update(self, data): """ Update the user associated with the api token. """ @@ -166,6 +189,35 @@ def update(self, data): class Workspaces(TogglObject, Get, Update): uri = '/workspaces' + def get_users(self, workspace_id): + """ Get the Users for the Workspace with the given ID. """ + return self.get_child_objects(workspace_id, '/users') + + def get_clients(self, workspace_id): + """ Get the Clients for the Workspace with the given ID. """ + return self.get_child_objects(workspace_id, '/clients') + + def get_projects(self, workspace_id): + """ Get the Projects for the Workspace with the given ID. """ + return self.get_child_objects(workspace_id, '/projects') + + def get_tasks(self, workspace_id): + """ Get the Tasks for the Workspace with the given ID. """ + return self.get_child_objects(workspace_id, '/tasks') + + def get_tags(self, workspace_id): + """ Get the Tags for the Workspace with the given ID. """ + return self.get_child_objects(workspace_id, '/tags') + + def get_workspace_users(self, workspace_id): + """ Get the Tags for the Workspace with the given ID. """ + return self.get_child_objects(workspace_id, '/workspace_users') + + def invite(self, workspace_id, data): + """ add users to workspace. """ + uri = '/workspaces/{workspace_id}/invite'.format(workspace_id) + self.toggl.post(uri, data) + class WorkspaceUsers(TogglObject, Update, Delete): uri = '/workspace_users' From c258ac4b5bf1c3ede5e9bf4dfbb27492c61ca653 Mon Sep 17 00:00:00 2001 From: aarose Date: Wed, 19 Aug 2015 15:58:28 -0400 Subject: [PATCH 12/26] Updated tests to match updated api --- togglwrapper/tests.py | 50 ++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/togglwrapper/tests.py b/togglwrapper/tests.py index 52a97b8..0fbeb7f 100644 --- a/togglwrapper/tests.py +++ b/togglwrapper/tests.py @@ -2,40 +2,50 @@ import unittest from mock import patch -from requests.exceptions import HTTPError import api +from errors import AuthError class TestAPIMethods(unittest.TestCase): def setUp(self): self.api_token = 'fake_token_1' + self.toggl = api.Toggl(self.api_token) + + def _get_json(self, filename): + """ Return the JSON data contained in the given filename. """ + with open('json/{}.json'.format(filename)) as json_file: + json_data = json.load(json_file) + json_file.close() + return json_data def test_client(self): """ Should successfully establish the client. """ - # Grab the JSON response to return - with open('json/user_get.json') as json_data: - data = json.load(json_data) - json_data.close() - toggl = api.Client(self.api_token) - with patch.object(toggl, 'User') as MockedUser: - MockedUser.get.return_value = data - response = toggl.User.get() - self.assertTrue(response) - self.assertEqual(response['data']['api_token'], self.api_token) + # TODO: patch futher upstream - in requests, probably. + with patch.object(self.toggl, 'User') as MockedUser: + MockedUser.get.return_value = self._get_json('user_get') + response = self.toggl.User.get() + self.assertTrue(response) + self.assertEqual(response['data']['api_token'], self.api_token) def test_client_wrong_token(self): """ Should raise exception when wrong API token is provided. """ - toggl = api.Client(self.api_token) - self.assertRaises(HTTPError, toggl.User.get) - # Check that the error is a 403 error - - def test_update_user(self): - """ Should change a property of the User. """ - - def test_update_user_incorrect(self): - """ Should raise a 404 error. """ + self.assertRaises(AuthError, self.toggl.User.get) + + def test_client_create(self): + """ Should create a new Client. """ + with patch.object(self.toggl, 'Clients') as MockedClients: + MockedClients.create.return_value = self._get_json('client_create') + response = self.toggl.Clients.create() + self.assertTrue(response) + + def test_client_get(self): + """ Should get a specific Client by ID. """ + with patch.object(self.toggl, 'Clients') as MockedClients: + MockedClients.get.return_value = self._get_json('client_get') + response = self.toggl.Clients.get() + self.assertTrue(response) if __name__ == '__main__': From 37bf538f4b24e788bf1df2bce5963ec85d6679f8 Mon Sep 17 00:00:00 2001 From: aarose Date: Wed, 19 Aug 2015 15:59:02 -0400 Subject: [PATCH 13/26] Updated init to import Toggl client --- togglwrapper/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/togglwrapper/__init__.py b/togglwrapper/__init__.py index e69de29..aa32408 100644 --- a/togglwrapper/__init__.py +++ b/togglwrapper/__init__.py @@ -0,0 +1 @@ +from api import Toggl From b3eadc09e6f27278b2dc0748cd695f33150523ec Mon Sep 17 00:00:00 2001 From: aarose Date: Fri, 21 Aug 2015 00:29:57 -0400 Subject: [PATCH 14/26] Updated requirements --- requirements.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/requirements.txt b/requirements.txt index ac2cf62..ab0edf6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,16 @@ +cffi==1.2.1 +cookies==2.2.1 +cryptography==1.0 +enum34==1.0.4 +funcsigs==0.4 +idna==2.0 +ipaddress==1.0.14 +mock==1.3.0 +ndg-httpsclient==0.4.0 +pbr==1.5.0 +pyasn1==0.1.8 +pycparser==2.14 +pyOpenSSL==0.15.1 requests==2.7.0 +responses==0.4.0 +six==1.9.0 From 8b198260693d6584fd82eb2e828fe580d3c254c4 Mon Sep 17 00:00:00 2001 From: aarose Date: Fri, 21 Aug 2015 00:30:33 -0400 Subject: [PATCH 15/26] Revamped tests to use responses and mock at the right level --- togglwrapper/tests.py | 99 ++++++++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 24 deletions(-) diff --git a/togglwrapper/tests.py b/togglwrapper/tests.py index 0fbeb7f..897da36 100644 --- a/togglwrapper/tests.py +++ b/togglwrapper/tests.py @@ -1,51 +1,102 @@ import json import unittest -from mock import patch +import responses import api from errors import AuthError -class TestAPIMethods(unittest.TestCase): +FAKE_TOKEN = 'fake_token_1' + + +class TestTogglBase(unittest.TestCase): + """ Class to establish utility methods for Test classes. """ + + api_token = FAKE_TOKEN + focus_class = None def setUp(self): - self.api_token = 'fake_token_1' self.toggl = api.Toggl(self.api_token) - def _get_json(self, filename): - """ Return the JSON data contained in the given filename. """ + def get_json(self, filename): + """ Return the JSON data contained in the given filename as a dict. """ with open('json/{}.json'.format(filename)) as json_file: - json_data = json.load(json_file) + json_dict = json.load(json_file) json_file.close() - return json_data + return json.dumps(json_dict) - def test_client(self): - """ Should successfully establish the client. """ - # TODO: patch futher upstream - in requests, probably. - with patch.object(self.toggl, 'User') as MockedUser: - MockedUser.get.return_value = self._get_json('user_get') - response = self.toggl.User.get() - self.assertTrue(response) - self.assertEqual(response['data']['api_token'], self.api_token) + @property + def full_url(self): + return self.toggl.api_url + self.focus_class.uri - def test_client_wrong_token(self): + +class TestToggl(TestTogglBase): + + @responses.activate + def test_wrong_token(self): """ Should raise exception when wrong API token is provided. """ + full_url = self.toggl.api_url + self.toggl.User.uri + responses.add(responses.GET, full_url, status=403) self.assertRaises(AuthError, self.toggl.User.get) + self.assertEqual(len(responses.calls), 1) + + +class TestClients(TestTogglBase): + focus_class = api.Clients - def test_client_create(self): + @responses.activate + def test_create(self): """ Should create a new Client. """ - with patch.object(self.toggl, 'Clients') as MockedClients: - MockedClients.create.return_value = self._get_json('client_create') - response = self.toggl.Clients.create() + responses.add( + responses.POST, + self.full_url, + body=self.get_json('client_create'), + status=200, + content_type='application/json' + ) + + new_client_data = {"client": {"name": "Very Big Company", "wid": 777}} + response = self.toggl.Clients.create(new_client_data) self.assertTrue(response) + self.assertEqual(len(responses.calls), 1) - def test_client_get(self): + @responses.activate + def test_get_by_id(self): """ Should get a specific Client by ID. """ - with patch.object(self.toggl, 'Clients') as MockedClients: - MockedClients.get.return_value = self._get_json('client_get') - response = self.toggl.Clients.get() + client_id = 1239455 + full_url = '{url}/{id}'.format(url=self.full_url, id=client_id) + responses.add( + responses.GET, + full_url, + body=self.get_json('client_get'), + status=200, + content_type='application/json' + ) + + response = self.toggl.Clients.get(id=client_id) + self.assertTrue(response) + self.assertEqual(len(responses.calls), 1) + + +class TestUser(TestTogglBase): + focus_class = api.User + + @responses.activate + def test_get(self): + """ Should successfully establish the client. """ + responses.add( + responses.GET, + self.full_url, + body=self.get_json('user_get'), + status=200, + content_type='application/json' + ) + + response = self.toggl.User.get() + self.assertTrue(response) + self.assertEqual(len(responses.calls), 1) if __name__ == '__main__': From 99c8fe1e1adc11a9705aa48f97143ace26a5a533 Mon Sep 17 00:00:00 2001 From: aarose Date: Fri, 21 Aug 2015 00:31:44 -0400 Subject: [PATCH 16/26] Fixed issue where errors were being silenced if the response did not have an error status code --- togglwrapper/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/togglwrapper/api.py b/togglwrapper/api.py index 18d744e..5f62c1e 100644 --- a/togglwrapper/api.py +++ b/togglwrapper/api.py @@ -16,7 +16,9 @@ def inner(*args, **kwargs): response = func(*args, **kwargs) try: return response.json() - except ValueError: + except ValueError, e: + if response.ok: + raise e # JSON couldn't be decoded, raise status error if response.status_code == 403: raise AuthError('Incorrect API token.') From b9282331c435460a321e3685418e7b7bfed0194c Mon Sep 17 00:00:00 2001 From: aarose Date: Fri, 21 Aug 2015 01:21:46 -0400 Subject: [PATCH 17/26] Renamed json dir to fixtures --- togglwrapper/fixtures/__init__.py | 0 togglwrapper/{json => fixtures}/client_create.json | 0 togglwrapper/{json => fixtures}/client_get.json | 0 togglwrapper/{json => fixtures}/client_update.json | 0 togglwrapper/{json => fixtures}/clients_get.json | 0 togglwrapper/{json => fixtures}/dashboard.json | 0 togglwrapper/{json => fixtures}/project_create.json | 0 togglwrapper/{json => fixtures}/project_get.json | 0 togglwrapper/{json => fixtures}/project_projectusers_get.json | 0 togglwrapper/{json => fixtures}/project_tasks_get.json | 0 togglwrapper/{json => fixtures}/project_update.json | 0 togglwrapper/{json => fixtures}/projectuser_create.json | 0 togglwrapper/{json => fixtures}/projectuser_update.json | 0 togglwrapper/{json => fixtures}/projectusers_create_multiple.json | 0 togglwrapper/{json => fixtures}/projectusers_get.json | 0 togglwrapper/{json => fixtures}/projectusers_update_multiple.json | 0 togglwrapper/{json => fixtures}/reset_token.json | 0 togglwrapper/{json => fixtures}/signups.json | 0 togglwrapper/{json => fixtures}/tag_create.json | 0 togglwrapper/{json => fixtures}/tag_update.json | 0 togglwrapper/{json => fixtures}/task_create.json | 0 togglwrapper/{json => fixtures}/task_get.json | 0 togglwrapper/{json => fixtures}/task_update.json | 0 togglwrapper/{json => fixtures}/tasks_update_multiple.json | 0 togglwrapper/{json => fixtures}/time_entries_get_in_range.json | 0 togglwrapper/{json => fixtures}/time_entries_update_multiple.json | 0 togglwrapper/{json => fixtures}/time_entry_create.json | 0 togglwrapper/{json => fixtures}/time_entry_current.json | 0 togglwrapper/{json => fixtures}/time_entry_get.json | 0 togglwrapper/{json => fixtures}/time_entry_start.json | 0 togglwrapper/{json => fixtures}/time_entry_stop.json | 0 togglwrapper/{json => fixtures}/time_entry_update.json | 0 togglwrapper/{json => fixtures}/user_get.json | 0 togglwrapper/{json => fixtures}/user_get_with_related_data.json | 0 togglwrapper/{json => fixtures}/user_update.json | 0 togglwrapper/{json => fixtures}/workspace_clients.json | 0 togglwrapper/{json => fixtures}/workspace_get.json | 0 togglwrapper/{json => fixtures}/workspace_invite.json | 0 togglwrapper/{json => fixtures}/workspace_projects.json | 0 togglwrapper/{json => fixtures}/workspace_tags.json | 0 togglwrapper/{json => fixtures}/workspace_tasks.json | 0 togglwrapper/{json => fixtures}/workspace_update.json | 0 togglwrapper/{json => fixtures}/workspace_users.json | 0 togglwrapper/{json => fixtures}/workspace_workspaceusers.json | 0 togglwrapper/{json => fixtures}/workspaces_get.json | 0 togglwrapper/{json => fixtures}/workspaceuser_update.json | 0 46 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 togglwrapper/fixtures/__init__.py rename togglwrapper/{json => fixtures}/client_create.json (100%) rename togglwrapper/{json => fixtures}/client_get.json (100%) rename togglwrapper/{json => fixtures}/client_update.json (100%) rename togglwrapper/{json => fixtures}/clients_get.json (100%) rename togglwrapper/{json => fixtures}/dashboard.json (100%) rename togglwrapper/{json => fixtures}/project_create.json (100%) rename togglwrapper/{json => fixtures}/project_get.json (100%) rename togglwrapper/{json => fixtures}/project_projectusers_get.json (100%) rename togglwrapper/{json => fixtures}/project_tasks_get.json (100%) rename togglwrapper/{json => fixtures}/project_update.json (100%) rename togglwrapper/{json => fixtures}/projectuser_create.json (100%) rename togglwrapper/{json => fixtures}/projectuser_update.json (100%) rename togglwrapper/{json => fixtures}/projectusers_create_multiple.json (100%) rename togglwrapper/{json => fixtures}/projectusers_get.json (100%) rename togglwrapper/{json => fixtures}/projectusers_update_multiple.json (100%) rename togglwrapper/{json => fixtures}/reset_token.json (100%) rename togglwrapper/{json => fixtures}/signups.json (100%) rename togglwrapper/{json => fixtures}/tag_create.json (100%) rename togglwrapper/{json => fixtures}/tag_update.json (100%) rename togglwrapper/{json => fixtures}/task_create.json (100%) rename togglwrapper/{json => fixtures}/task_get.json (100%) rename togglwrapper/{json => fixtures}/task_update.json (100%) rename togglwrapper/{json => fixtures}/tasks_update_multiple.json (100%) rename togglwrapper/{json => fixtures}/time_entries_get_in_range.json (100%) rename togglwrapper/{json => fixtures}/time_entries_update_multiple.json (100%) rename togglwrapper/{json => fixtures}/time_entry_create.json (100%) rename togglwrapper/{json => fixtures}/time_entry_current.json (100%) rename togglwrapper/{json => fixtures}/time_entry_get.json (100%) rename togglwrapper/{json => fixtures}/time_entry_start.json (100%) rename togglwrapper/{json => fixtures}/time_entry_stop.json (100%) rename togglwrapper/{json => fixtures}/time_entry_update.json (100%) rename togglwrapper/{json => fixtures}/user_get.json (100%) rename togglwrapper/{json => fixtures}/user_get_with_related_data.json (100%) rename togglwrapper/{json => fixtures}/user_update.json (100%) rename togglwrapper/{json => fixtures}/workspace_clients.json (100%) rename togglwrapper/{json => fixtures}/workspace_get.json (100%) rename togglwrapper/{json => fixtures}/workspace_invite.json (100%) rename togglwrapper/{json => fixtures}/workspace_projects.json (100%) rename togglwrapper/{json => fixtures}/workspace_tags.json (100%) rename togglwrapper/{json => fixtures}/workspace_tasks.json (100%) rename togglwrapper/{json => fixtures}/workspace_update.json (100%) rename togglwrapper/{json => fixtures}/workspace_users.json (100%) rename togglwrapper/{json => fixtures}/workspace_workspaceusers.json (100%) rename togglwrapper/{json => fixtures}/workspaces_get.json (100%) rename togglwrapper/{json => fixtures}/workspaceuser_update.json (100%) diff --git a/togglwrapper/fixtures/__init__.py b/togglwrapper/fixtures/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/togglwrapper/json/client_create.json b/togglwrapper/fixtures/client_create.json similarity index 100% rename from togglwrapper/json/client_create.json rename to togglwrapper/fixtures/client_create.json diff --git a/togglwrapper/json/client_get.json b/togglwrapper/fixtures/client_get.json similarity index 100% rename from togglwrapper/json/client_get.json rename to togglwrapper/fixtures/client_get.json diff --git a/togglwrapper/json/client_update.json b/togglwrapper/fixtures/client_update.json similarity index 100% rename from togglwrapper/json/client_update.json rename to togglwrapper/fixtures/client_update.json diff --git a/togglwrapper/json/clients_get.json b/togglwrapper/fixtures/clients_get.json similarity index 100% rename from togglwrapper/json/clients_get.json rename to togglwrapper/fixtures/clients_get.json diff --git a/togglwrapper/json/dashboard.json b/togglwrapper/fixtures/dashboard.json similarity index 100% rename from togglwrapper/json/dashboard.json rename to togglwrapper/fixtures/dashboard.json diff --git a/togglwrapper/json/project_create.json b/togglwrapper/fixtures/project_create.json similarity index 100% rename from togglwrapper/json/project_create.json rename to togglwrapper/fixtures/project_create.json diff --git a/togglwrapper/json/project_get.json b/togglwrapper/fixtures/project_get.json similarity index 100% rename from togglwrapper/json/project_get.json rename to togglwrapper/fixtures/project_get.json diff --git a/togglwrapper/json/project_projectusers_get.json b/togglwrapper/fixtures/project_projectusers_get.json similarity index 100% rename from togglwrapper/json/project_projectusers_get.json rename to togglwrapper/fixtures/project_projectusers_get.json diff --git a/togglwrapper/json/project_tasks_get.json b/togglwrapper/fixtures/project_tasks_get.json similarity index 100% rename from togglwrapper/json/project_tasks_get.json rename to togglwrapper/fixtures/project_tasks_get.json diff --git a/togglwrapper/json/project_update.json b/togglwrapper/fixtures/project_update.json similarity index 100% rename from togglwrapper/json/project_update.json rename to togglwrapper/fixtures/project_update.json diff --git a/togglwrapper/json/projectuser_create.json b/togglwrapper/fixtures/projectuser_create.json similarity index 100% rename from togglwrapper/json/projectuser_create.json rename to togglwrapper/fixtures/projectuser_create.json diff --git a/togglwrapper/json/projectuser_update.json b/togglwrapper/fixtures/projectuser_update.json similarity index 100% rename from togglwrapper/json/projectuser_update.json rename to togglwrapper/fixtures/projectuser_update.json diff --git a/togglwrapper/json/projectusers_create_multiple.json b/togglwrapper/fixtures/projectusers_create_multiple.json similarity index 100% rename from togglwrapper/json/projectusers_create_multiple.json rename to togglwrapper/fixtures/projectusers_create_multiple.json diff --git a/togglwrapper/json/projectusers_get.json b/togglwrapper/fixtures/projectusers_get.json similarity index 100% rename from togglwrapper/json/projectusers_get.json rename to togglwrapper/fixtures/projectusers_get.json diff --git a/togglwrapper/json/projectusers_update_multiple.json b/togglwrapper/fixtures/projectusers_update_multiple.json similarity index 100% rename from togglwrapper/json/projectusers_update_multiple.json rename to togglwrapper/fixtures/projectusers_update_multiple.json diff --git a/togglwrapper/json/reset_token.json b/togglwrapper/fixtures/reset_token.json similarity index 100% rename from togglwrapper/json/reset_token.json rename to togglwrapper/fixtures/reset_token.json diff --git a/togglwrapper/json/signups.json b/togglwrapper/fixtures/signups.json similarity index 100% rename from togglwrapper/json/signups.json rename to togglwrapper/fixtures/signups.json diff --git a/togglwrapper/json/tag_create.json b/togglwrapper/fixtures/tag_create.json similarity index 100% rename from togglwrapper/json/tag_create.json rename to togglwrapper/fixtures/tag_create.json diff --git a/togglwrapper/json/tag_update.json b/togglwrapper/fixtures/tag_update.json similarity index 100% rename from togglwrapper/json/tag_update.json rename to togglwrapper/fixtures/tag_update.json diff --git a/togglwrapper/json/task_create.json b/togglwrapper/fixtures/task_create.json similarity index 100% rename from togglwrapper/json/task_create.json rename to togglwrapper/fixtures/task_create.json diff --git a/togglwrapper/json/task_get.json b/togglwrapper/fixtures/task_get.json similarity index 100% rename from togglwrapper/json/task_get.json rename to togglwrapper/fixtures/task_get.json diff --git a/togglwrapper/json/task_update.json b/togglwrapper/fixtures/task_update.json similarity index 100% rename from togglwrapper/json/task_update.json rename to togglwrapper/fixtures/task_update.json diff --git a/togglwrapper/json/tasks_update_multiple.json b/togglwrapper/fixtures/tasks_update_multiple.json similarity index 100% rename from togglwrapper/json/tasks_update_multiple.json rename to togglwrapper/fixtures/tasks_update_multiple.json diff --git a/togglwrapper/json/time_entries_get_in_range.json b/togglwrapper/fixtures/time_entries_get_in_range.json similarity index 100% rename from togglwrapper/json/time_entries_get_in_range.json rename to togglwrapper/fixtures/time_entries_get_in_range.json diff --git a/togglwrapper/json/time_entries_update_multiple.json b/togglwrapper/fixtures/time_entries_update_multiple.json similarity index 100% rename from togglwrapper/json/time_entries_update_multiple.json rename to togglwrapper/fixtures/time_entries_update_multiple.json diff --git a/togglwrapper/json/time_entry_create.json b/togglwrapper/fixtures/time_entry_create.json similarity index 100% rename from togglwrapper/json/time_entry_create.json rename to togglwrapper/fixtures/time_entry_create.json diff --git a/togglwrapper/json/time_entry_current.json b/togglwrapper/fixtures/time_entry_current.json similarity index 100% rename from togglwrapper/json/time_entry_current.json rename to togglwrapper/fixtures/time_entry_current.json diff --git a/togglwrapper/json/time_entry_get.json b/togglwrapper/fixtures/time_entry_get.json similarity index 100% rename from togglwrapper/json/time_entry_get.json rename to togglwrapper/fixtures/time_entry_get.json diff --git a/togglwrapper/json/time_entry_start.json b/togglwrapper/fixtures/time_entry_start.json similarity index 100% rename from togglwrapper/json/time_entry_start.json rename to togglwrapper/fixtures/time_entry_start.json diff --git a/togglwrapper/json/time_entry_stop.json b/togglwrapper/fixtures/time_entry_stop.json similarity index 100% rename from togglwrapper/json/time_entry_stop.json rename to togglwrapper/fixtures/time_entry_stop.json diff --git a/togglwrapper/json/time_entry_update.json b/togglwrapper/fixtures/time_entry_update.json similarity index 100% rename from togglwrapper/json/time_entry_update.json rename to togglwrapper/fixtures/time_entry_update.json diff --git a/togglwrapper/json/user_get.json b/togglwrapper/fixtures/user_get.json similarity index 100% rename from togglwrapper/json/user_get.json rename to togglwrapper/fixtures/user_get.json diff --git a/togglwrapper/json/user_get_with_related_data.json b/togglwrapper/fixtures/user_get_with_related_data.json similarity index 100% rename from togglwrapper/json/user_get_with_related_data.json rename to togglwrapper/fixtures/user_get_with_related_data.json diff --git a/togglwrapper/json/user_update.json b/togglwrapper/fixtures/user_update.json similarity index 100% rename from togglwrapper/json/user_update.json rename to togglwrapper/fixtures/user_update.json diff --git a/togglwrapper/json/workspace_clients.json b/togglwrapper/fixtures/workspace_clients.json similarity index 100% rename from togglwrapper/json/workspace_clients.json rename to togglwrapper/fixtures/workspace_clients.json diff --git a/togglwrapper/json/workspace_get.json b/togglwrapper/fixtures/workspace_get.json similarity index 100% rename from togglwrapper/json/workspace_get.json rename to togglwrapper/fixtures/workspace_get.json diff --git a/togglwrapper/json/workspace_invite.json b/togglwrapper/fixtures/workspace_invite.json similarity index 100% rename from togglwrapper/json/workspace_invite.json rename to togglwrapper/fixtures/workspace_invite.json diff --git a/togglwrapper/json/workspace_projects.json b/togglwrapper/fixtures/workspace_projects.json similarity index 100% rename from togglwrapper/json/workspace_projects.json rename to togglwrapper/fixtures/workspace_projects.json diff --git a/togglwrapper/json/workspace_tags.json b/togglwrapper/fixtures/workspace_tags.json similarity index 100% rename from togglwrapper/json/workspace_tags.json rename to togglwrapper/fixtures/workspace_tags.json diff --git a/togglwrapper/json/workspace_tasks.json b/togglwrapper/fixtures/workspace_tasks.json similarity index 100% rename from togglwrapper/json/workspace_tasks.json rename to togglwrapper/fixtures/workspace_tasks.json diff --git a/togglwrapper/json/workspace_update.json b/togglwrapper/fixtures/workspace_update.json similarity index 100% rename from togglwrapper/json/workspace_update.json rename to togglwrapper/fixtures/workspace_update.json diff --git a/togglwrapper/json/workspace_users.json b/togglwrapper/fixtures/workspace_users.json similarity index 100% rename from togglwrapper/json/workspace_users.json rename to togglwrapper/fixtures/workspace_users.json diff --git a/togglwrapper/json/workspace_workspaceusers.json b/togglwrapper/fixtures/workspace_workspaceusers.json similarity index 100% rename from togglwrapper/json/workspace_workspaceusers.json rename to togglwrapper/fixtures/workspace_workspaceusers.json diff --git a/togglwrapper/json/workspaces_get.json b/togglwrapper/fixtures/workspaces_get.json similarity index 100% rename from togglwrapper/json/workspaces_get.json rename to togglwrapper/fixtures/workspaces_get.json diff --git a/togglwrapper/json/workspaceuser_update.json b/togglwrapper/fixtures/workspaceuser_update.json similarity index 100% rename from togglwrapper/json/workspaceuser_update.json rename to togglwrapper/fixtures/workspaceuser_update.json From 426eb314b938947195ab59d86729dd5acfc70a73 Mon Sep 17 00:00:00 2001 From: aarose Date: Fri, 21 Aug 2015 01:22:18 -0400 Subject: [PATCH 18/26] Renamed errors to exceptions --- togglwrapper/{errors.py => exceptions.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename togglwrapper/{errors.py => exceptions.py} (100%) diff --git a/togglwrapper/errors.py b/togglwrapper/exceptions.py similarity index 100% rename from togglwrapper/errors.py rename to togglwrapper/exceptions.py From 24411a62fb413551e275b2cdb05b693f9acbdc7c Mon Sep 17 00:00:00 2001 From: aarose Date: Fri, 21 Aug 2015 01:23:15 -0400 Subject: [PATCH 19/26] Fleshed out client tests and fixed path to json fixtures --- togglwrapper/tests.py | 53 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/togglwrapper/tests.py b/togglwrapper/tests.py index 897da36..3849c0f 100644 --- a/togglwrapper/tests.py +++ b/togglwrapper/tests.py @@ -21,7 +21,7 @@ def setUp(self): def get_json(self, filename): """ Return the JSON data contained in the given filename as a dict. """ - with open('json/{}.json'.format(filename)) as json_file: + with open('fixtures/{}.json'.format(filename)) as json_file: json_dict = json.load(json_file) json_file.close() return json.dumps(json_dict) @@ -78,13 +78,62 @@ def test_get_by_id(self): self.assertTrue(response) self.assertEqual(len(responses.calls), 1) + @responses.activate + def test_get(self): + """ Should get all Clients. """ + responses.add( + responses.GET, + self.full_url, + body=self.get_json('clients_get'), + status=200, + content_type='application/json' + ) + + response = self.toggl.Clients.get() + self.assertTrue(response) + self.assertEqual(len(responses.calls), 1) + + @responses.activate + def test_update(self): + """ Should update a Client. """ + client_id = 1239455 + full_url = '{url}/{id}'.format(url=self.full_url, id=client_id) + responses.add( + responses.PUT, + full_url, + body=self.get_json('client_update'), + status=200, + content_type='application/json' + ) + + update_data = { + "client": { + "name": "Very Big Company", + "notes": "something about the client" + } + } + response = self.toggl.Clients.update(id=client_id, data=update_data) + self.assertTrue(response) + self.assertEqual(len(responses.calls), 1) + + @responses.activate + def test_delete(self): + """ Should delete a Client. """ + client_id = 1239455 + full_url = '{url}/{id}'.format(url=self.full_url, id=client_id) + responses.add(responses.DELETE, full_url, status=200) + + response = self.toggl.Clients.delete(client_id) + self.assertTrue(response) + self.assertEqual(len(responses.calls), 1) + class TestUser(TestTogglBase): focus_class = api.User @responses.activate def test_get(self): - """ Should successfully establish the client. """ + """ Should successfully get the User associated with the token. """ responses.add( responses.GET, self.full_url, From b47968fc0de04fc5e634ca8795b5ceef56369fd9 Mon Sep 17 00:00:00 2001 From: aarose Date: Fri, 21 Aug 2015 01:24:34 -0400 Subject: [PATCH 20/26] Fixed syntax error with "any", refactored decorators, fixed syntax errors with string formatting --- togglwrapper/api.py | 45 ++++++++++++++++---------------------- togglwrapper/decorators.py | 22 +++++++++++++++++++ 2 files changed, 41 insertions(+), 26 deletions(-) create mode 100644 togglwrapper/decorators.py diff --git a/togglwrapper/api.py b/togglwrapper/api.py index 5f62c1e..da12c37 100644 --- a/togglwrapper/api.py +++ b/togglwrapper/api.py @@ -3,7 +3,7 @@ import requests from requests.auth import HTTPBasicAuth -from errors import AuthError +from .decorators import error_checking, return_json BASE_URL = 'https://www.toggl.com/api' @@ -11,21 +11,6 @@ API_URL = '{base}/{version}'.format(base=BASE_URL, version=API_VERSION) -def return_json_or_raise_error(func): - def inner(*args, **kwargs): - response = func(*args, **kwargs) - try: - return response.json() - except ValueError, e: - if response.ok: - raise e - # JSON couldn't be decoded, raise status error - if response.status_code == 403: - raise AuthError('Incorrect API token.') - response.raise_for_status() - return inner - - class TogglObject(object): @property @@ -71,19 +56,24 @@ def create(self, data): class Update(object): def update(self, id=None, ids=None, data=None): """ Update a specific instance by ID, or update multiple instances. """ - if not any(id, ids) or (id and ids): + if not any((id, ids)) or (id and ids): raise Exception('Must provide either an ID or an iterable of IDs.') if id is not None: - uri = '{uri}/{id}'.format(self.uri, id) + uri = '{uri}/{id}'.format(uri=self.uri, id=id) else: uri = '{uri}/{ids}'.format(uri=self.uri, ids=','.join(ids)) return self.toggl.put(uri, data) class Delete(object): - def delete(self, id): - """ Delete a specific instance by ID. """ - uri = '{uri}/{id}'.format(self.uri, id) + def delete(self, id=None, ids=None): + """ Delete a specific instance by ID, or delete multiple instances. """ + if not any((id, ids)) or (id and ids): + raise Exception('Must provide either an ID or an iterable of IDs.') + if id is not None: + uri = '{uri}/{id}'.format(uri=self.uri, id=id) + else: + uri = '{uri}/{ids}'.format(uri=self.uri, ids=','.join(ids)) return self.toggl.delete(uri) @@ -254,28 +244,31 @@ def reset_token(self): """ Delete the current API Token and use a new token. """ return self.post('/reset_token') - @return_json_or_raise_error + @return_json + @error_checking def get(self, uri, params=None): """ GET to the given uri. """ full_uri = '{base}{uri}'.format(base=self.api_url, uri=uri) return requests.get(full_uri, params=params, auth=self.auth) - @return_json_or_raise_error + @return_json + @error_checking def post(self, uri, data=None): """ POST to the given uri with a data dict. """ full_uri = '{base}{uri}'.format(base=self.api_url, uri=uri) payload = json.dumps(data) if data is not None else None return requests.post(full_uri, data=payload, auth=self.auth) - @return_json_or_raise_error + @return_json + @error_checking def put(self, uri, data): """ PUT to the given uri with a data dict. """ full_uri = '{base}{uri}'.format(base=self.api_url, uri=uri) payload = json.dumps(data) return requests.put(full_uri, data=payload, auth=self.auth) - @return_json_or_raise_error + @error_checking def delete(self, uri): """ DELETE to the given uri. """ - full_uri = '{base}{uri}'.format(self.api_url, uri) + full_uri = '{base}{uri}'.format(base=self.api_url, uri=uri) return requests.delete(full_uri, auth=self.auth) diff --git a/togglwrapper/decorators.py b/togglwrapper/decorators.py new file mode 100644 index 0000000..cd200bd --- /dev/null +++ b/togglwrapper/decorators.py @@ -0,0 +1,22 @@ +from .exceptions import AuthError + + +def return_json(func): + """ Returns the JSON content of a requests.Response. """ + def inner(*args, **kwargs): + response = func(*args, **kwargs) + return response.json() + return inner + + +def error_checking(func): + """ Raises exceptions if the response did not return 200 OK. """ + def inner(*args, **kwargs): + response = func(*args, **kwargs) + # Status code of 403 Forbidden means incorrect API token/wrong auth + if response.status_code == 403: + raise AuthError('Incorrect API token.') + # Raise an HTTPError if status code isn't 200 + response.raise_for_status() + return response + return inner From f7f39c29b0457b212de3f32d0bf1a4c527cc74cb Mon Sep 17 00:00:00 2001 From: aarose Date: Fri, 21 Aug 2015 01:59:47 -0400 Subject: [PATCH 21/26] Moved tests and fixtures out of the package dir --- .../fixtures => fixtures}/__init__.py | 0 .../fixtures => fixtures}/client_create.json | 0 .../fixtures => fixtures}/client_get.json | 0 .../fixtures => fixtures}/client_update.json | 0 .../fixtures => fixtures}/clients_get.json | 0 .../fixtures => fixtures}/dashboard.json | 0 .../fixtures => fixtures}/project_create.json | 0 .../fixtures => fixtures}/project_get.json | 0 .../project_projectusers_get.json | 0 .../project_tasks_get.json | 0 .../fixtures => fixtures}/project_update.json | 0 .../projectuser_create.json | 0 .../projectuser_update.json | 0 .../projectusers_create_multiple.json | 0 .../projectusers_get.json | 0 .../projectusers_update_multiple.json | 0 .../fixtures => fixtures}/reset_token.json | 0 .../fixtures => fixtures}/signups.json | 0 .../fixtures => fixtures}/tag_create.json | 0 .../fixtures => fixtures}/tag_update.json | 0 .../fixtures => fixtures}/task_create.json | 0 .../fixtures => fixtures}/task_get.json | 0 .../fixtures => fixtures}/task_update.json | 0 .../tasks_update_multiple.json | 0 .../time_entries_get_in_range.json | 0 .../time_entries_update_multiple.json | 0 .../time_entry_create.json | 0 .../time_entry_current.json | 0 .../fixtures => fixtures}/time_entry_get.json | 0 .../time_entry_start.json | 0 .../fixtures => fixtures}/time_entry_stop.json | 0 .../time_entry_update.json | 0 .../fixtures => fixtures}/user_get.json | 0 .../user_get_with_related_data.json | 0 .../fixtures => fixtures}/user_update.json | 0 .../workspace_clients.json | 0 .../fixtures => fixtures}/workspace_get.json | 0 .../workspace_invite.json | 0 .../workspace_projects.json | 0 .../fixtures => fixtures}/workspace_tags.json | 0 .../fixtures => fixtures}/workspace_tasks.json | 0 .../workspace_update.json | 0 .../fixtures => fixtures}/workspace_users.json | 0 .../workspace_workspaceusers.json | 0 .../fixtures => fixtures}/workspaces_get.json | 0 .../workspaceuser_update.json | 0 togglwrapper/tests.py => tests.py | 18 ++++++++++++------ 47 files changed, 12 insertions(+), 6 deletions(-) rename {togglwrapper/fixtures => fixtures}/__init__.py (100%) rename {togglwrapper/fixtures => fixtures}/client_create.json (100%) rename {togglwrapper/fixtures => fixtures}/client_get.json (100%) rename {togglwrapper/fixtures => fixtures}/client_update.json (100%) rename {togglwrapper/fixtures => fixtures}/clients_get.json (100%) rename {togglwrapper/fixtures => fixtures}/dashboard.json (100%) rename {togglwrapper/fixtures => fixtures}/project_create.json (100%) rename {togglwrapper/fixtures => fixtures}/project_get.json (100%) rename {togglwrapper/fixtures => fixtures}/project_projectusers_get.json (100%) rename {togglwrapper/fixtures => fixtures}/project_tasks_get.json (100%) rename {togglwrapper/fixtures => fixtures}/project_update.json (100%) rename {togglwrapper/fixtures => fixtures}/projectuser_create.json (100%) rename {togglwrapper/fixtures => fixtures}/projectuser_update.json (100%) rename {togglwrapper/fixtures => fixtures}/projectusers_create_multiple.json (100%) rename {togglwrapper/fixtures => fixtures}/projectusers_get.json (100%) rename {togglwrapper/fixtures => fixtures}/projectusers_update_multiple.json (100%) rename {togglwrapper/fixtures => fixtures}/reset_token.json (100%) rename {togglwrapper/fixtures => fixtures}/signups.json (100%) rename {togglwrapper/fixtures => fixtures}/tag_create.json (100%) rename {togglwrapper/fixtures => fixtures}/tag_update.json (100%) rename {togglwrapper/fixtures => fixtures}/task_create.json (100%) rename {togglwrapper/fixtures => fixtures}/task_get.json (100%) rename {togglwrapper/fixtures => fixtures}/task_update.json (100%) rename {togglwrapper/fixtures => fixtures}/tasks_update_multiple.json (100%) rename {togglwrapper/fixtures => fixtures}/time_entries_get_in_range.json (100%) rename {togglwrapper/fixtures => fixtures}/time_entries_update_multiple.json (100%) rename {togglwrapper/fixtures => fixtures}/time_entry_create.json (100%) rename {togglwrapper/fixtures => fixtures}/time_entry_current.json (100%) rename {togglwrapper/fixtures => fixtures}/time_entry_get.json (100%) rename {togglwrapper/fixtures => fixtures}/time_entry_start.json (100%) rename {togglwrapper/fixtures => fixtures}/time_entry_stop.json (100%) rename {togglwrapper/fixtures => fixtures}/time_entry_update.json (100%) rename {togglwrapper/fixtures => fixtures}/user_get.json (100%) rename {togglwrapper/fixtures => fixtures}/user_get_with_related_data.json (100%) rename {togglwrapper/fixtures => fixtures}/user_update.json (100%) rename {togglwrapper/fixtures => fixtures}/workspace_clients.json (100%) rename {togglwrapper/fixtures => fixtures}/workspace_get.json (100%) rename {togglwrapper/fixtures => fixtures}/workspace_invite.json (100%) rename {togglwrapper/fixtures => fixtures}/workspace_projects.json (100%) rename {togglwrapper/fixtures => fixtures}/workspace_tags.json (100%) rename {togglwrapper/fixtures => fixtures}/workspace_tasks.json (100%) rename {togglwrapper/fixtures => fixtures}/workspace_update.json (100%) rename {togglwrapper/fixtures => fixtures}/workspace_users.json (100%) rename {togglwrapper/fixtures => fixtures}/workspace_workspaceusers.json (100%) rename {togglwrapper/fixtures => fixtures}/workspaces_get.json (100%) rename {togglwrapper/fixtures => fixtures}/workspaceuser_update.json (100%) rename togglwrapper/tests.py => tests.py (87%) diff --git a/togglwrapper/fixtures/__init__.py b/fixtures/__init__.py similarity index 100% rename from togglwrapper/fixtures/__init__.py rename to fixtures/__init__.py diff --git a/togglwrapper/fixtures/client_create.json b/fixtures/client_create.json similarity index 100% rename from togglwrapper/fixtures/client_create.json rename to fixtures/client_create.json diff --git a/togglwrapper/fixtures/client_get.json b/fixtures/client_get.json similarity index 100% rename from togglwrapper/fixtures/client_get.json rename to fixtures/client_get.json diff --git a/togglwrapper/fixtures/client_update.json b/fixtures/client_update.json similarity index 100% rename from togglwrapper/fixtures/client_update.json rename to fixtures/client_update.json diff --git a/togglwrapper/fixtures/clients_get.json b/fixtures/clients_get.json similarity index 100% rename from togglwrapper/fixtures/clients_get.json rename to fixtures/clients_get.json diff --git a/togglwrapper/fixtures/dashboard.json b/fixtures/dashboard.json similarity index 100% rename from togglwrapper/fixtures/dashboard.json rename to fixtures/dashboard.json diff --git a/togglwrapper/fixtures/project_create.json b/fixtures/project_create.json similarity index 100% rename from togglwrapper/fixtures/project_create.json rename to fixtures/project_create.json diff --git a/togglwrapper/fixtures/project_get.json b/fixtures/project_get.json similarity index 100% rename from togglwrapper/fixtures/project_get.json rename to fixtures/project_get.json diff --git a/togglwrapper/fixtures/project_projectusers_get.json b/fixtures/project_projectusers_get.json similarity index 100% rename from togglwrapper/fixtures/project_projectusers_get.json rename to fixtures/project_projectusers_get.json diff --git a/togglwrapper/fixtures/project_tasks_get.json b/fixtures/project_tasks_get.json similarity index 100% rename from togglwrapper/fixtures/project_tasks_get.json rename to fixtures/project_tasks_get.json diff --git a/togglwrapper/fixtures/project_update.json b/fixtures/project_update.json similarity index 100% rename from togglwrapper/fixtures/project_update.json rename to fixtures/project_update.json diff --git a/togglwrapper/fixtures/projectuser_create.json b/fixtures/projectuser_create.json similarity index 100% rename from togglwrapper/fixtures/projectuser_create.json rename to fixtures/projectuser_create.json diff --git a/togglwrapper/fixtures/projectuser_update.json b/fixtures/projectuser_update.json similarity index 100% rename from togglwrapper/fixtures/projectuser_update.json rename to fixtures/projectuser_update.json diff --git a/togglwrapper/fixtures/projectusers_create_multiple.json b/fixtures/projectusers_create_multiple.json similarity index 100% rename from togglwrapper/fixtures/projectusers_create_multiple.json rename to fixtures/projectusers_create_multiple.json diff --git a/togglwrapper/fixtures/projectusers_get.json b/fixtures/projectusers_get.json similarity index 100% rename from togglwrapper/fixtures/projectusers_get.json rename to fixtures/projectusers_get.json diff --git a/togglwrapper/fixtures/projectusers_update_multiple.json b/fixtures/projectusers_update_multiple.json similarity index 100% rename from togglwrapper/fixtures/projectusers_update_multiple.json rename to fixtures/projectusers_update_multiple.json diff --git a/togglwrapper/fixtures/reset_token.json b/fixtures/reset_token.json similarity index 100% rename from togglwrapper/fixtures/reset_token.json rename to fixtures/reset_token.json diff --git a/togglwrapper/fixtures/signups.json b/fixtures/signups.json similarity index 100% rename from togglwrapper/fixtures/signups.json rename to fixtures/signups.json diff --git a/togglwrapper/fixtures/tag_create.json b/fixtures/tag_create.json similarity index 100% rename from togglwrapper/fixtures/tag_create.json rename to fixtures/tag_create.json diff --git a/togglwrapper/fixtures/tag_update.json b/fixtures/tag_update.json similarity index 100% rename from togglwrapper/fixtures/tag_update.json rename to fixtures/tag_update.json diff --git a/togglwrapper/fixtures/task_create.json b/fixtures/task_create.json similarity index 100% rename from togglwrapper/fixtures/task_create.json rename to fixtures/task_create.json diff --git a/togglwrapper/fixtures/task_get.json b/fixtures/task_get.json similarity index 100% rename from togglwrapper/fixtures/task_get.json rename to fixtures/task_get.json diff --git a/togglwrapper/fixtures/task_update.json b/fixtures/task_update.json similarity index 100% rename from togglwrapper/fixtures/task_update.json rename to fixtures/task_update.json diff --git a/togglwrapper/fixtures/tasks_update_multiple.json b/fixtures/tasks_update_multiple.json similarity index 100% rename from togglwrapper/fixtures/tasks_update_multiple.json rename to fixtures/tasks_update_multiple.json diff --git a/togglwrapper/fixtures/time_entries_get_in_range.json b/fixtures/time_entries_get_in_range.json similarity index 100% rename from togglwrapper/fixtures/time_entries_get_in_range.json rename to fixtures/time_entries_get_in_range.json diff --git a/togglwrapper/fixtures/time_entries_update_multiple.json b/fixtures/time_entries_update_multiple.json similarity index 100% rename from togglwrapper/fixtures/time_entries_update_multiple.json rename to fixtures/time_entries_update_multiple.json diff --git a/togglwrapper/fixtures/time_entry_create.json b/fixtures/time_entry_create.json similarity index 100% rename from togglwrapper/fixtures/time_entry_create.json rename to fixtures/time_entry_create.json diff --git a/togglwrapper/fixtures/time_entry_current.json b/fixtures/time_entry_current.json similarity index 100% rename from togglwrapper/fixtures/time_entry_current.json rename to fixtures/time_entry_current.json diff --git a/togglwrapper/fixtures/time_entry_get.json b/fixtures/time_entry_get.json similarity index 100% rename from togglwrapper/fixtures/time_entry_get.json rename to fixtures/time_entry_get.json diff --git a/togglwrapper/fixtures/time_entry_start.json b/fixtures/time_entry_start.json similarity index 100% rename from togglwrapper/fixtures/time_entry_start.json rename to fixtures/time_entry_start.json diff --git a/togglwrapper/fixtures/time_entry_stop.json b/fixtures/time_entry_stop.json similarity index 100% rename from togglwrapper/fixtures/time_entry_stop.json rename to fixtures/time_entry_stop.json diff --git a/togglwrapper/fixtures/time_entry_update.json b/fixtures/time_entry_update.json similarity index 100% rename from togglwrapper/fixtures/time_entry_update.json rename to fixtures/time_entry_update.json diff --git a/togglwrapper/fixtures/user_get.json b/fixtures/user_get.json similarity index 100% rename from togglwrapper/fixtures/user_get.json rename to fixtures/user_get.json diff --git a/togglwrapper/fixtures/user_get_with_related_data.json b/fixtures/user_get_with_related_data.json similarity index 100% rename from togglwrapper/fixtures/user_get_with_related_data.json rename to fixtures/user_get_with_related_data.json diff --git a/togglwrapper/fixtures/user_update.json b/fixtures/user_update.json similarity index 100% rename from togglwrapper/fixtures/user_update.json rename to fixtures/user_update.json diff --git a/togglwrapper/fixtures/workspace_clients.json b/fixtures/workspace_clients.json similarity index 100% rename from togglwrapper/fixtures/workspace_clients.json rename to fixtures/workspace_clients.json diff --git a/togglwrapper/fixtures/workspace_get.json b/fixtures/workspace_get.json similarity index 100% rename from togglwrapper/fixtures/workspace_get.json rename to fixtures/workspace_get.json diff --git a/togglwrapper/fixtures/workspace_invite.json b/fixtures/workspace_invite.json similarity index 100% rename from togglwrapper/fixtures/workspace_invite.json rename to fixtures/workspace_invite.json diff --git a/togglwrapper/fixtures/workspace_projects.json b/fixtures/workspace_projects.json similarity index 100% rename from togglwrapper/fixtures/workspace_projects.json rename to fixtures/workspace_projects.json diff --git a/togglwrapper/fixtures/workspace_tags.json b/fixtures/workspace_tags.json similarity index 100% rename from togglwrapper/fixtures/workspace_tags.json rename to fixtures/workspace_tags.json diff --git a/togglwrapper/fixtures/workspace_tasks.json b/fixtures/workspace_tasks.json similarity index 100% rename from togglwrapper/fixtures/workspace_tasks.json rename to fixtures/workspace_tasks.json diff --git a/togglwrapper/fixtures/workspace_update.json b/fixtures/workspace_update.json similarity index 100% rename from togglwrapper/fixtures/workspace_update.json rename to fixtures/workspace_update.json diff --git a/togglwrapper/fixtures/workspace_users.json b/fixtures/workspace_users.json similarity index 100% rename from togglwrapper/fixtures/workspace_users.json rename to fixtures/workspace_users.json diff --git a/togglwrapper/fixtures/workspace_workspaceusers.json b/fixtures/workspace_workspaceusers.json similarity index 100% rename from togglwrapper/fixtures/workspace_workspaceusers.json rename to fixtures/workspace_workspaceusers.json diff --git a/togglwrapper/fixtures/workspaces_get.json b/fixtures/workspaces_get.json similarity index 100% rename from togglwrapper/fixtures/workspaces_get.json rename to fixtures/workspaces_get.json diff --git a/togglwrapper/fixtures/workspaceuser_update.json b/fixtures/workspaceuser_update.json similarity index 100% rename from togglwrapper/fixtures/workspaceuser_update.json rename to fixtures/workspaceuser_update.json diff --git a/togglwrapper/tests.py b/tests.py similarity index 87% rename from togglwrapper/tests.py rename to tests.py index 3849c0f..365f49d 100644 --- a/togglwrapper/tests.py +++ b/tests.py @@ -1,13 +1,15 @@ import json +import os import unittest import responses -import api -from errors import AuthError +from togglwrapper import api +from togglwrapper.exceptions import AuthError FAKE_TOKEN = 'fake_token_1' +FIXTURES_PATH = '%s/fixtures' % os.path.dirname(os.path.abspath(__file__)) class TestTogglBase(unittest.TestCase): @@ -20,11 +22,14 @@ def setUp(self): self.toggl = api.Toggl(self.api_token) def get_json(self, filename): - """ Return the JSON data contained in the given filename as a dict. """ - with open('fixtures/{}.json'.format(filename)) as json_file: + """ Return the JSON data in the .json file with the given filename. """ + file_path = '{path}/{filename}.json'.format(path=FIXTURES_PATH, + filename=filename) + with open(file_path) as json_file: json_dict = json.load(json_file) json_file.close() - return json.dumps(json_dict) + raw_json = json.dumps(json_dict) + return raw_json @property def full_url(self): @@ -148,5 +153,6 @@ def test_get(self): self.assertEqual(len(responses.calls), 1) -if __name__ == '__main__': +if __name__ == '__main__' and __package__ is None: + __package__ = "toggl" unittest.main() From 62842f882b8c738635b86ead43b9592e5a2f1e2a Mon Sep 17 00:00:00 2001 From: aarose Date: Fri, 21 Aug 2015 02:00:23 -0400 Subject: [PATCH 22/26] Made get_child_objects a more private-feeling method --- togglwrapper/api.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/togglwrapper/api.py b/togglwrapper/api.py index da12c37..57388b1 100644 --- a/togglwrapper/api.py +++ b/togglwrapper/api.py @@ -29,7 +29,7 @@ def _compile_uri(self, id=None): uri += '/%s' % id return uri - def get_child_objects(self, parent_id, child_uri, params=None): + def _get_child_objects(self, parent_id, child_uri, params=None): """ Get the Objects that belong to the parent Object with the given ID. @@ -96,7 +96,7 @@ def get_projects(self, client_id, active=True): raise Exception("The 'active' param must be either True, False,", "or 'both'.") params = {'active': active} - return self.get_child_objects(client_id, '/projects', params=params) + return self._get_child_objects(client_id, '/projects', params=params) class Dashboard(TogglObject, Get): @@ -116,11 +116,11 @@ def get(self, project_id): def get_project_users(self, project_id): """ Get the ProjectUsers for the Project with the given ID. """ - return self.get_child_objects(project_id, '/project_users') + return self._get_child_objects(project_id, '/project_users') def get_tasks(self, project_id): """ Get the Tasks for the Project with the given ID. """ - return self.get_child_objects(project_id, '/tasks') + return self._get_child_objects(project_id, '/tasks') class ProjectUsers(TogglObject, Create, Update, Delete): @@ -183,27 +183,27 @@ class Workspaces(TogglObject, Get, Update): def get_users(self, workspace_id): """ Get the Users for the Workspace with the given ID. """ - return self.get_child_objects(workspace_id, '/users') + return self._get_child_objects(workspace_id, '/users') def get_clients(self, workspace_id): """ Get the Clients for the Workspace with the given ID. """ - return self.get_child_objects(workspace_id, '/clients') + return self._get_child_objects(workspace_id, '/clients') def get_projects(self, workspace_id): """ Get the Projects for the Workspace with the given ID. """ - return self.get_child_objects(workspace_id, '/projects') + return self._get_child_objects(workspace_id, '/projects') def get_tasks(self, workspace_id): """ Get the Tasks for the Workspace with the given ID. """ - return self.get_child_objects(workspace_id, '/tasks') + return self._get_child_objects(workspace_id, '/tasks') def get_tags(self, workspace_id): """ Get the Tags for the Workspace with the given ID. """ - return self.get_child_objects(workspace_id, '/tags') + return self._get_child_objects(workspace_id, '/tags') def get_workspace_users(self, workspace_id): """ Get the Tags for the Workspace with the given ID. """ - return self.get_child_objects(workspace_id, '/workspace_users') + return self._get_child_objects(workspace_id, '/workspace_users') def invite(self, workspace_id, data): """ add users to workspace. """ From 2cac2cddbff8cf8fd02c85b63bfc97774ce318ba Mon Sep 17 00:00:00 2001 From: aarose Date: Fri, 21 Aug 2015 02:05:45 -0400 Subject: [PATCH 23/26] updated init to have basic logging and import right --- togglwrapper/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/togglwrapper/__init__.py b/togglwrapper/__init__.py index aa32408..1b12be9 100644 --- a/togglwrapper/__init__.py +++ b/togglwrapper/__init__.py @@ -1 +1,6 @@ -from api import Toggl +# -*- coding: utf-8 -*- + +from .api import Toggl + +import logging +logging.getLogger(__name__) From 05852703346ff94d259a55979f81bf8e28e3480d Mon Sep 17 00:00:00 2001 From: aarose Date: Fri, 21 Aug 2015 02:08:14 -0400 Subject: [PATCH 24/26] Removed init from fixtures --- fixtures/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 fixtures/__init__.py diff --git a/fixtures/__init__.py b/fixtures/__init__.py deleted file mode 100644 index e69de29..0000000 From 061fc60e57e1a37780faca0cddd7776ebb97f8a2 Mon Sep 17 00:00:00 2001 From: aarose Date: Fri, 21 Aug 2015 02:19:29 -0400 Subject: [PATCH 25/26] Fixed syntax error with "any" --- togglwrapper/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/togglwrapper/api.py b/togglwrapper/api.py index 57388b1..66dde0c 100644 --- a/togglwrapper/api.py +++ b/togglwrapper/api.py @@ -92,7 +92,7 @@ def get_projects(self, client_id, active=True): cond1 = (active is True) cond2 = (active is False) cond3 = (active is 'both') - if not any(cond1, cond2, cond3): + if not any((cond1, cond2, cond3)): raise Exception("The 'active' param must be either True, False,", "or 'both'.") params = {'active': active} From 3a00b0b6b6a5fe2ad3563c43ab7ad1688c1b8e8f Mon Sep 17 00:00:00 2001 From: aarose Date: Fri, 21 Aug 2015 02:20:20 -0400 Subject: [PATCH 26/26] Added basic test for getting client projects --- fixtures/client_projects_get.json | 21 +++++++++++++++++++++ tests.py | 17 +++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 fixtures/client_projects_get.json diff --git a/fixtures/client_projects_get.json b/fixtures/client_projects_get.json new file mode 100644 index 0000000..1c46b57 --- /dev/null +++ b/fixtures/client_projects_get.json @@ -0,0 +1,21 @@ +[ + { + "id":909, + "wid":777, + "cid":987, + "name":"Very lucrative project", + "billable":false, + "is_private":true, + "active":true, + "at":"2013-03-06T09:15:18+00:00" + },{ + "id":32143, + "wid":777, + "cid":987, + "name":"Factory server infrastructure", + "billable":true, + "is_private":true, + "active":true, + "at":"2013-03-06T09:16:06+00:00" + } +] diff --git a/tests.py b/tests.py index 365f49d..7ee4dca 100644 --- a/tests.py +++ b/tests.py @@ -132,6 +132,23 @@ def test_delete(self): self.assertTrue(response) self.assertEqual(len(responses.calls), 1) + @responses.activate + def test_projects_get(self): + """ Should get all active projects under the Client. """ + client_id = 1239455 + url = '{url}/{id}/projects'.format(url=self.full_url, id=client_id) + responses.add( + responses.GET, + url, + body=self.get_json('client_projects_get'), + status=200, + content_type='application/json' + ) + + response = self.toggl.Clients.get_projects(client_id) + self.assertTrue(response) + self.assertEqual(len(responses.calls), 1) + class TestUser(TestTogglBase): focus_class = api.User