bump version of wakatime package to 2.0.0

This commit is contained in:
Alan Hamlett 2014-05-25 17:27:52 -07:00
parent 533aaac313
commit 7e678a38bd
5 changed files with 166 additions and 20 deletions

View file

@ -3,6 +3,18 @@ History
------- -------
2.0.0 (2014-05-25)
++++++++++++++++++
- offline time logging using sqlite3 to queue editor events
1.0.2 (2014-05-06)
++++++++++++++++++
- ability to set project from command line argument
1.0.1 (2014-03-05) 1.0.1 (2014-03-05)
++++++++++++++++++ ++++++++++++++++++

View file

@ -1,9 +1,9 @@
WakaTime WakaTime
======== ========
Fully automatic time tracking for your text editor. Fully automatic time tracking for programmers.
This is the common interface for the WakaTime api. You shouldn't need to directly use this package unless you are creating a new plugin. This is the common interface for the WakaTime api. You shouldn't need to directly use this package unless you are creating a new plugin or your text editor's plugin asks you to install the wakatime-cli interface.
Go to http://wakatime.com to install the plugin for your text editor. Go to http://wakatime.com to install the plugin for your text editor.
@ -11,4 +11,10 @@ Go to http://wakatime.com to install the plugin for your text editor.
Installation Installation
------------ ------------
https://wakatime.com/help/plugins/installing-plugins pip install wakatime
Usage
-----
https://wakatime.com/

View file

@ -11,13 +11,10 @@ setup(
name='wakatime', name='wakatime',
version=VERSION, version=VERSION,
license='BSD 3 Clause', license='BSD 3 Clause',
description=' '.join([ description='Interface to the WakaTime api.',
'Action event appender for WakaTime, a time',
'tracking api for text editors.',
]),
long_description=open('README.rst').read(), long_description=open('README.rst').read(),
author='Alan Hamlett', author='Alan Hamlett',
author_email='alan.hamlett@gmail.com', author_email='alan@wakatime.com',
url='https://github.com/wakatime/wakatime', url='https://github.com/wakatime/wakatime',
packages=packages, packages=packages,
package_dir={'wakatime': 'wakatime'}, package_dir={'wakatime': 'wakatime'},
@ -28,7 +25,7 @@ setup(
'console_scripts': ['wakatime = wakatime.__init__:main'], 'console_scripts': ['wakatime = wakatime.__init__:main'],
}, },
classifiers=( classifiers=(
'Development Status :: 3 - Alpha', 'Development Status :: 5 - Production/Stable',
'Environment :: Console', 'Environment :: Console',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License', 'License :: OSI Approved :: BSD License',

View file

@ -13,10 +13,10 @@
from __future__ import print_function from __future__ import print_function
__title__ = 'wakatime' __title__ = 'wakatime'
__version__ = '1.0.1' __version__ = '2.0.0'
__author__ = 'Alan Hamlett' __author__ = 'Alan Hamlett'
__license__ = 'BSD' __license__ = 'BSD'
__copyright__ = 'Copyright 2013 Alan Hamlett' __copyright__ = 'Copyright 2014 Alan Hamlett'
import base64 import base64
@ -39,6 +39,7 @@ except ImportError:
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'packages')) sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'packages'))
from .queue import Queue
from .log import setup_logging from .log import setup_logging
from .project import find_project from .project import find_project
from .stats import get_file_stats from .stats import get_file_stats
@ -148,9 +149,14 @@ def parseArguments(argv):
parser.add_argument('--plugin', dest='plugin', parser.add_argument('--plugin', dest='plugin',
help='optional text editor plugin name and version '+ help='optional text editor plugin name and version '+
'for User-Agent header') 'for User-Agent header')
parser.add_argument('--project', dest='project_name',
help='optional project name; will auto-discover by default')
parser.add_argument('--key', dest='key', parser.add_argument('--key', dest='key',
help='your wakatime api key; uses api_key from '+ help='your wakatime api key; uses api_key from '+
'~/.wakatime.conf by default') '~/.wakatime.conf by default')
parser.add_argument('--disableoffline', dest='offline',
action='store_false',
help='disables offline time logging instead of queuing logged time')
parser.add_argument('--ignore', dest='ignore', action='append', parser.add_argument('--ignore', dest='ignore', action='append',
help='filename patterns to ignore; POSIX regex syntax; can be used more than once') help='filename patterns to ignore; POSIX regex syntax; can be used more than once')
parser.add_argument('--logfile', dest='logfile', parser.add_argument('--logfile', dest='logfile',
@ -191,6 +197,8 @@ def parseArguments(argv):
args.ignore.append(pattern) args.ignore.append(pattern)
except TypeError: except TypeError:
pass pass
if args.offline and configs.has_option('settings', 'offline'):
args.offline = configs.getboolean('settings', 'offline')
if not args.verbose and configs.has_option('settings', 'verbose'): if not args.verbose and configs.has_option('settings', 'verbose'):
args.verbose = configs.getboolean('settings', 'verbose') args.verbose = configs.getboolean('settings', 'verbose')
if not args.verbose and configs.has_option('settings', 'debug'): if not args.verbose and configs.has_option('settings', 'debug'):
@ -225,8 +233,8 @@ def get_user_agent(plugin):
return user_agent return user_agent
def send_action(project=None, branch=None, stats={}, key=None, targetFile=None, def send_action(project=None, branch=None, stats=None, key=None, targetFile=None,
timestamp=None, isWrite=None, plugin=None, **kwargs): timestamp=None, isWrite=None, plugin=None, offline=None, **kwargs):
url = 'https://wakatime.com/api/v1/actions' url = 'https://wakatime.com/api/v1/actions'
log.debug('Sending action to api at %s' % url) log.debug('Sending action to api at %s' % url)
data = { data = {
@ -268,6 +276,12 @@ def send_action(project=None, branch=None, stats={}, key=None, targetFile=None,
} }
if log.isEnabledFor(logging.DEBUG): if log.isEnabledFor(logging.DEBUG):
exception_data['traceback'] = traceback.format_exc() exception_data['traceback'] = traceback.format_exc()
if offline:
queue = Queue()
queue.push(data, plugin)
if log.isEnabledFor(logging.DEBUG):
log.warn(exception_data)
else:
log.error(exception_data) log.error(exception_data)
except: except:
exception_data = { exception_data = {
@ -275,6 +289,12 @@ def send_action(project=None, branch=None, stats={}, key=None, targetFile=None,
} }
if log.isEnabledFor(logging.DEBUG): if log.isEnabledFor(logging.DEBUG):
exception_data['traceback'] = traceback.format_exc() exception_data['traceback'] = traceback.format_exc()
if offline:
queue = Queue()
queue.push(data, plugin)
if log.isEnabledFor(logging.DEBUG):
log.warn(exception_data)
else:
log.error(exception_data) log.error(exception_data)
else: else:
if response.getcode() == 201: if response.getcode() == 201:
@ -282,6 +302,15 @@ def send_action(project=None, branch=None, stats={}, key=None, targetFile=None,
'response_code': response.getcode(), 'response_code': response.getcode(),
}) })
return True return True
if offline:
queue = Queue()
queue.push(data, plugin)
if log.isEnabledFor(logging.DEBUG):
log.warn({
'response_code': response.getcode(),
'response_content': response.read(),
})
else:
log.error({ log.error({
'response_code': response.getcode(), 'response_code': response.getcode(),
'response_content': response.read(), 'response_content': response.read(),
@ -313,7 +342,7 @@ def main(argv=None):
project_name = None project_name = None
if project: if project:
branch = project.branch() branch = project.branch()
project_name = project.name() project_name = args.project_name or project.name()
if send_action( if send_action(
project=project_name, project=project_name,
@ -321,6 +350,15 @@ def main(argv=None):
stats=stats, stats=stats,
**vars(args) **vars(args)
): ):
queue = Queue()
while True:
action = queue.pop()
if action is None:
break
if not send_action(project=action['project'], targetFile=action['file'], timestamp=action['time'],
branch=action['branch'], stats={'language': action['language'], 'lines': action['lines']},
key=args.key, isWrite=action['is_write'], plugin=action['plugin'], offline=args.offline):
break
return 0 # success return 0 # success
return 102 # api error return 102 # api error

View file

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
"""
wakatime.queue
~~~~~~~~~~~~~~
Queue for offline time logging.
http://wakatime.com
:copyright: (c) 2014 Alan Hamlett.
:license: BSD, see LICENSE for more details.
"""
import logging
import os
import sqlite3
from time import sleep
log = logging.getLogger(__name__)
class Queue(object):
DB_FILE = os.path.join(os.path.expanduser('~'), '.wakatime.db')
def connect(self):
exists = os.path.exists(self.DB_FILE)
conn = sqlite3.connect(self.DB_FILE)
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS action (
file text,
time real,
project text,
language text,
lines integer,
branch text,
is_write integer,
plugin text)
''')
return (conn, c)
def push(self, data, plugin):
conn, c = self.connect()
action = {
'file': data.get('file'),
'time': data.get('time'),
'project': data.get('project'),
'language': data.get('language'),
'lines': data.get('lines'),
'branch': data.get('branch'),
'is_write': 1 if data.get('is_write') else 0,
'plugin': plugin,
}
c.execute('INSERT INTO action VALUES (:file,:time,:project,:language,:lines,:branch,:is_write,:plugin)', action)
conn.commit()
conn.close()
def pop(self):
tries = 3
wait = 0.1
action = None
conn, c = self.connect()
loop = True
while loop and tries > -1:
try:
c.execute('BEGIN IMMEDIATE')
c.execute('SELECT * FROM action LIMIT 1')
row = c.fetchone()
if row is not None:
c.execute('''DELETE FROM action WHERE
file=? AND time=? AND project=? AND language=? AND
lines=? AND branch=? AND is_write=?''', row[0:7])
conn.commit()
if row is not None:
action = {
'file': row[0],
'time': row[1],
'project': row[2],
'language': row[3],
'lines': row[4],
'branch': row[5],
'is_write': True if row[6] is 1 else False,
'plugin': row[7],
}
loop = False
except sqlite3.Error, e:
log.debug(str(e))
sleep(wait)
tries -= 1
conn.close()
return action