Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgraded to Toggl's API v8 #4

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .toggl
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
# Email
# Password
# Client
# Project.
# Project
# Workspace.
#
# These will be your default settings, and can be overwritten with a
# These will be your default settings, and can be overwritten with a
# ~/.toggl_project in the working directory.

# Log in credentials for use with the Toggl command line client.
Email: [email protected]

# You don't have to specify your password here. If you don't, the
# You don't have to specify your password here. If you don't, the
# program will request it at run time.
Password: YourPassword
3 changes: 2 additions & 1 deletion toggl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# Toggl API. (https://www.toggl.com/public/api)
# end.
from toggl_cli.functions import *
from toggl_cli.global_vars import *
import sys, os

menuStr = '''
Expand Down Expand Up @@ -37,7 +38,7 @@ while True:
if response == 1:
print "Getting recent Tasks..."
entries = get_recent("tasks", ["name", "project", "id"])

# If an empty list, there are no tasks. Just get outta here!
if len(entries) == 0:
print "ERROR:No tasks were found. Returning to main menu."
Expand Down
121 changes: 76 additions & 45 deletions toggl_cli/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import getpass, requests, os, time, datetime, sys, select, termios, tty
from itertools import groupby


# Resolve simplejson discrepancy
try: import simplejson
except ImportError: import json as simplejson
Expand All @@ -14,7 +15,7 @@

def api(key, params=None):
'''
Return the API url call.
Return the API url call.
Potential keys:
me
time_entries
Expand All @@ -31,33 +32,33 @@ def api(key, params=None):
'''

if params:
apiCall = API_PREFIX + key + ".json?" + urlencode(params)
apiCall = API_PREFIX + key + urlencode(params)
else:
apiCall = API_PREFIX + key + ".json"
apiCall = API_PREFIX + key
return apiCall

def session(headers=None):
'''
Session wrapper for convenience
'''
r = requests.Session()
r.auth=AUTH
if headers:
return requests.session(auth=AUTH, headers=headers)
else:
return requests.session(auth=AUTH)
r.headers = headers
return r


def get_data(key):
'''
Get data from API. See list of keys in the api function in
this document.

'''
with session() as r:
response = r.get(api(key))

content = response.content
if response.ok:
json = simplejson.loads(content)["data"]
json = simplejson.loads(content)

# Reverse the list to get correct chronological order. Also,
# remove duplicates. But make sure this is actually a list!
Expand All @@ -81,19 +82,18 @@ def send_data(key, params=None, data=None):
data=simplejson.dumps(data)
with session(headers=headers) as r:
response = r.post(api(key), data=data)

content = response.content
if response.ok:
json = simplejson.loads(content)
return json["data"]
return json
else:
exit("Please verify your login credentials...")
exit("API Error: " + response.content)


def get_data_where(api, dataPair):
'''
Output the dicionary of a specific datakey (such as 'name') with a
value (such as 'My Weekend Project' for a given apikey
value (such as 'My Weekend Project' for a given apikey
(such as 'projects')
'''
data = get_data(api)
Expand All @@ -107,7 +107,8 @@ def get_data_where(api, dataPair):

# Data is an array of dicts. See if we find our datakey. If so,
# return it. If not, return false.

if data is None:
return None
for x in data:
if dataPair in x.items():
returnList.append(x)
Expand All @@ -123,17 +124,17 @@ def test_api(key):
def get_recent(apikey, keyList, numEntries=9):
'''
Gets the latest instances of apikey that corresponds to the current
project.
project.

keyList: List of keys you want to extract from the api call.
keyList: List of keys you want to extract from the api call.
numEntries: Number of entries to get.

ex: get_recent("time_entries") returns recent time entries
ex: get_recent("time_entries") returns recent time entries
for the project.

'''
project = get_project(small=True)
entries = get_data_where(apikey, {"project":project})
entries = get_data_where(apikey, {"pid":project["id"]})

# Remove duplicates

Expand All @@ -142,15 +143,15 @@ def get_recent(apikey, keyList, numEntries=9):
else:
recent = remove_dups(entries, "description")
return recent[0:numEntries]

def print_entries(entries, description, numToPrint=10):
'''
Pretty print entries. Since some api calls have the description as
Pretty print entries. Since some api calls have the description as
"description" and others have it as "name", we'll need to specify it
'''

# Ew, a bunch of string formatting
strLength = 60
strLength = 60
counter = 1
maxCounterLen = len(str(numToPrint)) + 2
width = str(strLength + maxCounterLen)
Expand All @@ -162,17 +163,17 @@ def print_entries(entries, description, numToPrint=10):
if len(d) >= strLength - 3:
d = d[0:strLength-3] + "..."

p = x["project"]["client_project_name"]
#p = x["project"]["client_project_name"]
print fmtString.format(str(counter) + ".", d)
counter += 1

def new_time_entry(description, taskID=False):
'''
'''
Creates a new time entry. Pass in a description string.
If this is a task (the PRO feature), set task to True.
If this is a task (the PRO feature), set task to True.
'''

# Get the project ID of the client/project pair specified in
# Get the project ID of the client/project pair specified in
# .toggl_project. Make sure it's valid now before they start the timer
# or they'll waste time in the event it's invalid
try:
Expand All @@ -184,7 +185,7 @@ def new_time_entry(description, taskID=False):
else:
print "The project " + TOGGL["PROJECT"] + " was not found"
exit("Exiting...")



# Get the current time and store it. Then pause until the user
Expand All @@ -211,14 +212,13 @@ def new_time_entry(description, taskID=False):
end_time = datetime.datetime.utcnow()
time_difference = (end_time - start_time).seconds


# Data passed to the request
data = {
"duration": time_difference,
"start": start_time.isoformat(),
"stop": "null",
"start": start_time.isoformat() + "Z",
#"stop": "null",
"created_with": "Python Command Line Client",
"project": {"id":projectID},
"pid": projectID,
"description": description}

# If task Id was specified, add it to the data dict
Expand All @@ -228,13 +228,12 @@ def new_time_entry(description, taskID=False):
# Add to time_entry key
data = {"time_entry" : data }


send_data("time_entries", data=data)
print "Success."

def dashes(string):
'''
Return a string of dashes the length of string. Just for pretty
Return a string of dashes the length of string. Just for pretty
formatting
'''
return "-" * len(string)
Expand Down Expand Up @@ -267,13 +266,13 @@ def parse_file(fileLoc):

def get_settings_from_file(fileLoc, theDict):
'''
parses file at fileLoc and searches for key:value pairs
parses file at fileLoc and searches for key:value pairs

Alters theDict dictionary
Alters theDict dictionary
'''
fileContents = parse_file(fileLoc)
for line in fileContents:
# Store the key value pair. Uppercase Key since it will be
# Store the key value pair. Uppercase Key since it will be
# used in a global variable
tmp = line.split(":")
key = tmp[0].strip().upper()
Expand All @@ -285,7 +284,7 @@ def timer_start_print(description, time):
Print a message to let the user know that the timer has started
and how to stop it
'''

print "\n"
print "=" * 50
print "Timer started!"
Expand All @@ -299,7 +298,7 @@ def timer_start_print(description, time):
print time.strftime("Started at: %I:%M%p")
print dashes(description)
print "Press Enter to stop timer... (CTRL-C to cancel)"

def get_project(small=False):
'''
Get the dictionary of the project specified in the .toggl_project file.
Expand All @@ -308,23 +307,55 @@ def get_project(small=False):
small as True returns a project dict containing only id, name,
and client_project_name. Useful for passing as a parameter
'''
workspace = get_workspace()
apikey = "workspaces/" + str(workspace["id"]) + "/projects"
if "CLIENT" in TOGGL.keys():
# It's stored Client - Project under the API
tmp = TOGGL["CLIENT"] +" - "+ TOGGL["PROJECT"]
project = get_data_where("projects", {"client_project_name":tmp})
project = get_data_where(apikey, {"client_project_name":tmp})
else:
project = get_data_where("projects", {"name":TOGGL["PROJECT"]})

project = get_data_where(apikey, {"name":TOGGL["PROJECT"]})
if project is None:
exit("No projects found in this workspace")
elif project.__len__() == 0:
exit("Project " + TOGGL["PROJECT"] + " not found in workspace " + workspace["name"])

# Should only be one response
project = project[0]

if small:
return {"id":project["id"],
"name":project["name"],
"client_project_name": project["client_project_name"]}
return {"id":project["id"],
"name":project["name"]
}
else:
return project

def get_workspace(small=False):
'''
Gets the correct workspace.
'''
data = get_data("workspaces")
if data.__len__() == 1:
workspace = data[0]
else:
if "WORKSPACE" in TOGGL.keys():
workspace_name = TOGGL["WORKSPACE"]
workspace = False
for x in data:
if (x["name"] == workspace_name):
workspace = x
if workspace == False:
print workspace_name
exit("Workspace " + workspace_name + " not found for user.")
else:
exit("No workspace specified")
if small:
return {"id":workspace["id"],
"name":workspace["name"]
}
else:
return workspace

def getkey():
'''
Get keystroke without having to press enter. *NIX only
Expand All @@ -341,7 +372,7 @@ def remove_dups(theList, key):
pass in a list of dictionaries theList and get back theList without
duplicates

See stackoverflow: http://bit.ly/tmwTIm
See stackoverflow: http://bit.ly/tmwTIm
'''
keyfunc = lambda d: (d[key])
giter = groupby(sorted(theList, key=keyfunc), keyfunc)
Expand Down
2 changes: 1 addition & 1 deletion toggl_cli/global_vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
TOGGL["PASSWORD"] = getpass.getpass("Your password: ")

# API convenience vars
API_PREFIX = "https://www.toggl.com/api/v6/"
API_PREFIX = "https://www.toggl.com/api/v8/"
AUTH = (TOGGL["EMAIL"], TOGGL["PASSWORD"])

# Task prompt
Expand Down
2 changes: 2 additions & 0 deletions toggl_project_example.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
# Password
# Client
# Project.
# Workspace
#
# These will overwrite the settings in your ~/.toggl file.

#Project: New Website
#Client: New Client
#Workspace: Workspace