diff --git a/.gitignore b/.gitignore index ff88fc0..387e025 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,13 @@ *.pyc -tests_live_config.json \ No newline at end of file +tests_live_config.json + +# Setuptools distribution folder. +/dist/ +/build/ + +# Python egg metadata, regenerated from source files by setuptools. +/*.egg-info + +# Others +*.swp +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index 7f188a2..b296b0f 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,11 @@ There is already a similar library, [toggl_target](https://github.com/mos3abof/t # Usage -The library is currently not in a Python PIP package yet. For now simply download to a location of your choice and do the following. +## Pypi Package + pip install python-toggl + +## Manual installation +Download to a location of your choice and do the following. ```python diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..1a17825 --- /dev/null +++ b/README.rst @@ -0,0 +1,99 @@ +toggl-python-api-client +======================= + +`Toggl `__ is an "insanely simple time tracking" +service. + +This specific library is a Python-based REST client to interface with +the Toggle API utilizing +`requests `__. + +This library is a pure api client to help other python apps interface +with Toggl. I created this project primarily to help with a bigger +internal project that I am doing at work while at the same time getting +my feet wet in Python. + +Others out there +================ + +There is already a similar library, +`toggl\_target `__ though it +is more of an application and the client api is not separate repo. Some +ideas/concepts on the client api were taken from there but I had needed +a client api only to help interface with my bigger app. + +Usage +===== + +Pypi Package +------------ + +:: + + pip install python-toggl + +Manual installation +------------------- + +Download to a location of your choice and do the following. + +.. code:: python + + + from toggl-python-api-client.api_client import TogglClientApi + + settings = { + 'token': 'xxx', + 'user_agent': 'your app name' + } + toggle_client = TogglClientApi(settings) + + response = toggle_client.get_workspaces() + +Dependencies +============ + +- Python 2.7 onwards +- `requests `__ + +Tests Dependencies +------------------ + +To run the tests, you will need the following packages + +- unittest +- json +- `httpretty `__ + +Tests +===== + +Tests created under ``/tests`` are primarily integration tests and are +not strictly unit tests. They consists of an offline and online(live) +test. + +Offline +------- + +``tests/tests_offline.py`` + +These tests are for the logic of the api client. They do not connect to +the actual Toggl servers - instead use +`httpretty `__ to mock the +responses. Sample responses are included in ``tests/json_responses`` and +are based on Toggle responses for V8 of the main api and V2 of the +report api. + +Online/Live +----------- + +``tests/tests_live.py`` + +These tests are to check the connections to Toggl's API and to ensure +that the client is handling the live responses from Toggl as expected. + +To avoid adding sensitive data to version control, no api credentials +have been included. To enable live tests, - make a copy of +``tests/tests_live_config.json.sample`` as +``tests/tests_live_config.json`` - update the settings on +``tests/tests_live_config.json`` as needed \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..4c3152f --- /dev/null +++ b/setup.py @@ -0,0 +1,22 @@ +from setuptools import setup + + +def readme(): + with open('README.rst') as f: + return f.read() + +setup(name='python-toggl', + version='0.1.4', + description='Python Wrapper for Toggl API', + long_description=readme(), + url='https://github.com/swappsco/toggl-python-api-client', + author='mechastorm', + author_email='dev@swapps.co', + license='MIT', + packages=['toggl'], + install_requires=[ + 'requests', + ], + test_suite='nose.collector', + tests_require=['nose', 'httpretty'], + zip_safe=False) diff --git a/tests/tests_live.py b/tests/tests_live.py index 6d82ee5..3d63b11 100644 --- a/tests/tests_live.py +++ b/tests/tests_live.py @@ -1,6 +1,6 @@ import unittest import json -from ..api_client import TogglClientApi +from toggl.api_client import TogglClientApi class TogglClientApiLiveTests(unittest.TestCase): @@ -21,7 +21,7 @@ def tearDown(self): def test_api_client_instance_created(self): self.assertNotEqual(self.api, None) - + def test_valid_toggl_base_url(self): self.assertEqual(self.api.api_base_url, 'https://www.toggl.com/api/v8') diff --git a/tests/tests_offline.py b/tests/tests_offline.py index cdbcd73..4107bda 100644 --- a/tests/tests_offline.py +++ b/tests/tests_offline.py @@ -1,7 +1,8 @@ import unittest import httpretty import json -from ..api_client import TogglClientApi +from toggl.api_client import TogglClientApi + class ToogleClientApiTests(unittest.TestCase): @@ -33,7 +34,7 @@ def tearDown(self): httpretty.disable() httpretty.reset() - def load_json_file(self, location, base_path='json_responses'): + def load_json_file(self, location, base_path='tests/json_responses'): file_contents = open(base_path+'/'+location+'.json') json_data = json.load(file_contents) file_contents.close() diff --git a/toggl/__init__.py b/toggl/__init__.py new file mode 100644 index 0000000..220455b --- /dev/null +++ b/toggl/__init__.py @@ -0,0 +1 @@ +from toggl import api_client \ No newline at end of file diff --git a/api_client.py b/toggl/api_client.py similarity index 77% rename from api_client.py rename to toggl/api_client.py index 77312d0..74b4c47 100644 --- a/api_client.py +++ b/toggl/api_client.py @@ -1,5 +1,5 @@ import requests - +from datetime import date class TogglClientApi: @@ -26,6 +26,8 @@ def __init__(self, credentials): self.api_report_base_url = self.build_api_url(self.credentials['base_url_report'], self.credentials['ver_report']) self.api_token = self.credentials['token'] self.api_username = self.credentials['username'] + self.user_agent = self.credentials['user_agent'] + self.workspace_id = int(self.credentials['workspace_id']) return @staticmethod @@ -47,10 +49,13 @@ def get_workspace_by_name(self, name): return workspace_found def get_workspaces(self): - return self.query('/workspaces'); + return self.query('/workspaces') + + def get_projects(self): + return self.query('/workspaces/%i/projects' % self.workspace_id) def get_workspace_members(self, workspace_id): - response = self.query('/workspaces/'+str(workspace_id)+'/workspace_users'); + response = self.query('/workspaces/'+str(workspace_id)+'/workspace_users') return response """ @@ -78,6 +83,26 @@ def get_user_hours_range(self, user_agent, workspace_id, user_id, start_date, en return time_total + """ + @param start_date datetime.date() + @param end_date datetime.date() + """"" + def get_project_times(self, project_id, start_date, end_date): + params = { + 'workspace_id': self.workspace_id, + 'project_ids': project_id, + 'since': start_date.strftime('%Y-%m-%d'), + 'until': end_date.strftime('%Y-%m-%d'), + 'user_agent': self.user_agent, + 'grouping': 'users', + 'subgrouping': 'projects' + } + time_entries_response = self.query_report('/details', params) + + json_response = time_entries_response.json() + + return json_response + def query_report(self, url, params={}, method='GET'): return self._query(self.api_report_base_url, url, params, method) @@ -100,6 +125,8 @@ def _query(self, base_url, url, params, method): @staticmethod def _do_get_query(url, headers, auth, params): + print url + print params response = requests.get(url, headers=headers, auth=auth, params=params) return response \ No newline at end of file