commit ee7d7c5559793169c394eb39972fc5c0a58399a5 Author: Tobias Date: Wed Aug 12 03:24:23 2020 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..90e0368 --- /dev/null +++ b/.gitignore @@ -0,0 +1,142 @@ +config.json + +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# End of https://www.toptal.com/developers/gitignore/api/python diff --git a/main.py b/main.py new file mode 100755 index 0000000..ec98ccf --- /dev/null +++ b/main.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# + +from bluepy import btle +from toggl.TogglPy import Toggl +import struct +import json +import dateutil.parser +from datetime import datetime, timezone + +import logging +_log = logging.getLogger(__name__) +_log.addHandler(logging.StreamHandler()) +_log.setLevel(logging.INFO) + + +def _ZEI_UUID(short_uuid): + print( 'c7e7%04X-c847-11e6-8175-8c89a55d403c' % (short_uuid)) + return 'c7e7%04X-c847-11e6-8175-8c89a55d403c' % (short_uuid) + + +class ZeiCharBase: + + def __init__(self, periph): + self.periph = periph + self.hndl = None + + def enable(self): + _svc = self.periph.getServiceByUUID(self.svcUUID) + _chr = _svc.getCharacteristics(self.charUUID)[0] + self.hndl = _chr.getHandle() + + # this is uint16_t - see: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml + _cccd = _chr.getDescriptors(btle.AssignedNumbers.client_characteristic_configuration)[0] + _cccd.write(struct.pack(" 0 ) + fields = ['total_count', 'total_currencies', 'total_billable', 'data'] + for f in fields: + self.assertTrue(f in d.keys()) + data = d['data'] + self.assertTrue(len(data)>0) + dr = data[0] + self.assertTrue('client' in dr) + self.assertTrue('start' in dr) + self.assertTrue('end' in dr) + self.assertTrue('task' in dr) + self.assertTrue('user' in dr) + self.assertTrue('project' in dr) + +if __name__ == '__main__': + unittest.main() diff --git a/toggl/toggl2gsuite.py b/toggl/toggl2gsuite.py new file mode 100644 index 0000000..f93599c --- /dev/null +++ b/toggl/toggl2gsuite.py @@ -0,0 +1,66 @@ +import os +import unittest + +import gspread +from oauth2client.service_account import ServiceAccountCredentials +from toggl.TogglPy import Toggl + +#this test demonstrates how to link up the toggl API into a google sheet +#in order to do this, you'll need to first setup your google account developer environment +#to do this, you can follow the instructions here: http://tinaja.computer/2017/10/27/gspread.html +#additional information about the spread API here: https://github.com/burnash/gspread + +#as such, to run this test you'll need to define the following env variables +#TOGGL_API_KEY : your toggl api key +#WORKSPACE_ID: a workspace id that you'd like to dump data for +#KEYFILE: the full path to your google suite keyfile (keep this secret/safe!) +#SHEET_URL: the url of the google sheet you are writing to + +class Toggl2GSuiteTest(unittest.TestCase): + def setUp(self): + self.api_key = os.environ['TOGGL_API_KEY'] + self.toggl = Toggl() + self.toggl.setAPIKey(self.api_key) + + # see https://stackoverflow.com/questions/19153462/get-excel-style-column-names-from-column-number + LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + + @staticmethod + def excel_style(row, col): + """ Convert given row and column number to an Excel-style cell name. """ + result = [] + while col: + col, rem = divmod(col - 1, 26) + result[:0] = Toggl2GSuiteTest.LETTERS[rem] + return ''.join(result) + str(row) + + def test_toggl2gsuite(self): + # have to do this year by year + data = { + 'workspace_id': os.environ['WORKSPACE_ID'], + } + y = self.toggl.getDetailedReport(data) + + + credentials = ServiceAccountCredentials.from_json_keyfile_name( + os.environ['KEYFILE'], + ['https://spreadsheets.google.com/feeds']) + + client = gspread.authorize(credentials) + sheet = client.open_by_url(os.environ['SHEET_URL']) + worksheet = sheet.get_worksheet(0) + + wrote_header = False + columns_to_write = ['user', 'updated', 'start', 'end', 'client', 'project', 'description', 'is_billable', + 'billable'] + cell_row = 0 + for row_idx, rec in enumerate(y['data']): + if wrote_header == False: + for col_idx, header in enumerate(columns_to_write): + worksheet.update_acell(Toggl2GSuiteTest.excel_style(row_idx + 1, col_idx + 1), header) + wrote_header = True + for col_idx, header in enumerate(columns_to_write): + worksheet.update_acell(Toggl2GSuiteTest.excel_style(row_idx + 2, col_idx + 1), rec[header]) + +if __name__ == '__main__': + unittest.main()