Skip to content

Commit 9be382c

Browse files
Initial commit.
1 parent 825dd0e commit 9be382c

File tree

6 files changed

+159
-0
lines changed

6 files changed

+159
-0
lines changed

CONTRIBUTING.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Contributions are welcome, including code, documentation, and examples.
2+
3+
GitHub issues and PRs are the preferred way to receive contributions.

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,28 @@
11
# tempo-api-python-client
22
Python bindings for Tempo (https://tempo-io.github.io/tempo-api-docs/)
3+
4+
This is a Tempo API client library to simplify the interaction with Tempo timesheets.
5+
6+
7+
## Getting Started
8+
9+
```
10+
from tempoapiclient import client
11+
12+
tempo = client.Tempo(
13+
"https://api.tempo.io/core/3",
14+
"<your_tempo_api_key>")
15+
16+
worklogs = tempo.get_worklogs("2019-11-10", "2019-11-11")
17+
18+
for i in worklogs:
19+
print(i)
20+
```
21+
22+
## Changelog
23+
24+
- 2019-12-17: Initial version with /worklogs, /work-attributes and /user-schedule
25+
26+
## Contributing
27+
28+
Contribution is welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for more details.

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
requests==2.22.0
2+
six==1.10.0

setup.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import setuptools
2+
3+
with open("README.md", "r") as fh:
4+
long_description = fh.read()
5+
6+
setuptools.setup(
7+
name="tempo-api-python-client",
8+
version="0.0.1",
9+
author="Stanislav Ulrych",
10+
author_email="[email protected]",
11+
description="Python bindings for Tempo (https://tempo-io.github.io/tempo-api-docs/)",
12+
long_description=long_description,
13+
long_description_content_type="text/markdown",
14+
url="https://github.com/stanislavulrych/tempo-api-python-client",
15+
packages=setuptools.find_packages(),
16+
classifiers=[
17+
"Programming Language :: Python :: 3",
18+
"License :: OSI Approved :: Apache Software License",
19+
"Operating System :: OS Independent",
20+
],
21+
python_requires='>=3.6',
22+
)

tempoapiclient/__init__.py

Whitespace-only changes.

tempoapiclient/client.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
13+
14+
from __future__ import unicode_literals
15+
16+
import itertools
17+
import requests
18+
import six
19+
20+
from calendar import monthrange
21+
from datetime import date, datetime
22+
from django.utils.dateparse import parse_datetime, parse_date
23+
from six.moves.urllib.parse import quote
24+
25+
26+
class Tempo(object):
27+
# Maximum number of result in single response (pagination)
28+
# NOTE: maximum number allowed by API is 1000
29+
MAX_RESULTS = 1000
30+
31+
def __init__(self, base_url, auth_token):
32+
self._token = auth_token
33+
self.BASE_URL = base_url
34+
35+
def _resolve_date(self, value):
36+
if isinstance(value, datetime):
37+
return value.date()
38+
if isinstance(value, date):
39+
return value
40+
parsed = parse_date(value)
41+
if not parsed:
42+
parsed = parse_datetime(value)
43+
if not parsed:
44+
raise ValueError()
45+
return parsed.date()
46+
return parsed
47+
48+
def _list(self, url, **params):
49+
# Resolve parameters
50+
url = self.BASE_URL + url
51+
headers = { "Authorization": "Bearer {}".format(self._token) }
52+
params = params.copy()
53+
54+
# Return paginated results
55+
processed = 0
56+
while True:
57+
params["offset"] = processed
58+
response = requests.get(url, params=params, headers=headers)
59+
response.raise_for_status()
60+
data = response.json()
61+
metadata = data.get("metadata", {})
62+
results = data.get("results", [])
63+
processed += metadata.get("count", 0)
64+
for result in results:
65+
yield result
66+
if "next" not in metadata or metadata.get("count", 0) < metadata.get("limit", 0):
67+
break
68+
69+
def _get_work_attributes(self):
70+
return { x["key"]: x for x in self._list("/work-attributes") }
71+
72+
def _iterate_worklogs(self, date_from, date_to, user=None):
73+
work_attributes = None
74+
date_from = self._resolve_date(date_from).isoformat()
75+
date_to = self._resolve_date(date_to).isoformat()
76+
url = "/worklogs"
77+
if user is not None:
78+
url += "/user/{}".format(user)
79+
params = { "from": date_from, "to": date_to, "limit": self.MAX_RESULTS }
80+
for worklog in self._list(url, **params):
81+
attributes = (worklog.get("attributes") or {}).get("values") or []
82+
resolved_attributes = {}
83+
if attributes:
84+
if work_attributes is None:
85+
work_attributes = self._get_work_attributes()
86+
for attribute in attributes:
87+
key = attribute["key"]
88+
name = work_attributes.get(key, {}).get("name", key)
89+
resolved_attributes[name] = attribute["value"]
90+
worklog["attributes"] = resolved_attributes
91+
yield worklog
92+
93+
def get_worklogs(self, date_from, date_to, user=None):
94+
return list(self._iterate_worklogs(date_from, date_to, user))
95+
96+
def _iterate_user_schedule(self, date_from, date_to, user):
97+
date_from = self._resolve_date(date_from).isoformat()
98+
date_to = self._resolve_date(date_to).isoformat()
99+
url = "/user-schedule"
100+
if user is not None:
101+
url += "/{}".format(user)
102+
params = { "from": date_from, "to": date_to, "limit": self.MAX_RESULTS }
103+
return self._list(url, **params)
104+
105+
def get_user_schedule(self, date_from, date_to, user):
106+
return list(self.iterate_schedule(date_from, date_to, user))

0 commit comments

Comments
 (0)