def test_stop_simple(self): # empty time entry raises an exception self.assertRaises(Exception, self.entry.stop) # non-running entry raises an exception self.entry.set('duration', 10) self.assertRaises(Exception, self.entry.stop) # missing an id raises an exception self.entry.set('duration', -10) self.assertRaises(Exception, self.entry.stop) # start an entry now self.entry = toggl.TimeEntry(description=desc('stop')) self.entry.start() # find it entry = self.find_time_entry(desc('stop')) self.assertIsNotNone(entry) # stop it entry.stop() # find it again entry = self.find_time_entry(desc('stop')) # make sure duration is positive. we can't be more specific because # we don't know the lag between us and toggl. self.assertGreaterEqual(entry.get('duration'), 0)
def resync_to_toggl(session, date_start, date_end): q = session.query(entry, toggl_id_map) if date_start: q = q.filter(entry.end_time >= totimestamp(date_start)) if date_end: q = q.filter(entry.start_time <= totimestamp(date_end)) q = q.filter(entry.id == toggl_id_map.entry_id) q = q.order_by(sqlalchemy.desc(entry.start_time)) for result in q: e = result.entry e_map = result.toggl_id_map tags = [w for w in e.description.split() if w and w[0] in '+@'] project = get_project(e.sheet, [tag[1:] for tag in tags if tag.startswith('+')]) if project is None: logging.warning("Could not work out project name for %s: not updating", e.description) continue toggl_data = {'description': e.description, 'pid': project['id'], 'created_with': 'tb-toggl', 'start': fd(e.start), 'stop': fd(e.end), 'duration': e.duration.seconds, 'tags': tags, 'id': e_map.toggl_id} toggl_entry = toggl.TimeEntry(data_dict=toggl_data) toggl_entry.data['create_with'] = 'tb-toggl' try: r = toggl.toggl("%s/time_entries/%s" % (toggl.TOGGL_URL, toggl_entry.data['id']), 'put', data=toggl_entry.json()) except Exception, error: logging.error("Error synchronizing: %s with data %r", error, toggl_data) continue toggl_dict = json.loads(r)["data"] toggl_id, toggl_at = toggl_dict['id'], toggl_dict['at'] if toggl_id != e_map.toggl_id: logging.warning("map mismatch: %r %r", toggl_dict, e_map) else: e_map.toggl_at = toggl_at logging.info("Updated toggl id %s from entry %s (%s to %s) at %s: %s", toggl_id, e.id, e.start, e.end, toggl_at, e.description) session.commit()
def send_to_toggl(session, date_start, date_end): q = session.query(entry) if date_start: q = q.filter(entry.end_time >= totimestamp(date_start)) if date_end: q = q.filter(entry.start_time <= totimestamp(date_end)) synced_entries = session.query(toggl_id_map.entry_id) q = q.filter(~entry.id.in_(synced_entries)) q = q.order_by(sqlalchemy.desc(entry.start_time)) for e in q: if e.description is None: logging.warning("Entry id %s from %s to %s has no description: not adding", e.id, e.start, e.end) continue tags = [w for w in e.description.split() if w and w[0] in '+@'] project = get_project(e.sheet, [tag[1:] for tag in tags if tag.startswith('+')]) if project is None: logging.warning("Could not work out project name for id %s (%s to %s) - description %s: not adding", e.id, e.start, e.end, e.description) continue toggl_data = {'description': e.description, 'pid': project['id'], 'start': fd(e.start), 'stop': fd(e.end), 'duration': e.duration.seconds, 'tags': tags} toggl_entry = toggl.TimeEntry(data_dict=toggl_data) toggl_entry.data['create_with'] = 'tb-toggl' try: r = toggl_entry.add() except Exception, error: logging.error("Error synchronizing: %s with data %r", error, toggl_data) continue if r is None: logging.error("Error adding (None returned): %r", toggl_data) continue toggl_dict = json.loads(r)["data"] toggl_id, toggl_at = toggl_dict['id'], toggl_dict['at'] new_map = toggl_id_map(entry_id=e.id, toggl_id=toggl_id, toggl_at=toggl_at) session.add(new_map) session.commit() logging.info("Mapped entry %s (%s to %s) to toggl id %s: %s", e.id, e.start, e.end, toggl_id, e.description)
def test_start_simple(self): # test with simpliest entry self.entry = toggl.TimeEntry(description=desc('start')) self.entry.start() orig_start = self.entry.get('start') # fetch the entry from toggl and compare with what we created entry = self.find_time_entry(desc('start')) self.assertIsNotNone(entry)
def test_start_simple(self): # empty time entry raises an exception self.assertRaises(Exception, self.entry.start) # test with simpliest entry self.entry = toggl.TimeEntry(description=desc('start')) self.entry.start() orig_duration = int(self.entry.get('duration')) entry = self.find_time_entry(desc('start')) self.assertIsNotNone(entry) # round duration to nearest integer self.assertEqual(entry.get('duration'), orig_duration)
def test_start_complex(self): # test with preset start time one hour ago UTC one_hour_ago = pytz.UTC.localize(datetime.datetime.utcnow() - datetime.timedelta(hours=1)) self.entry = toggl.TimeEntry(description=desc('start2'), start_time=one_hour_ago) self.entry.start() orig_duration = self.entry.get('duration') # see what toggl has entry = self.find_time_entry(desc('start2')) self.assertIsNotNone(entry) # toggl duration should be 1 hour self.assertGreaterEqual(entry.normalized_duration(), 3600)
def test_add(self): # time entry has no data, raises an exception self.assertRaises(Exception, self.entry.add) # create basic entry and add it start_time = toggl.DateAndTime().now() self.entry = toggl.TimeEntry(description=desc('add'), start_time=start_time, duration=10) self.entry.add() # make sure it shows up in the list entry = self.find_time_entry(desc('add')) self.assertIsNotNone(entry) self.assertEquals(entry.get('duration'), 10)
def test_delete(self): # start a time entry self.entry = toggl.TimeEntry(description=desc('delete')) self.entry.start() # deleting an entry without an id is an error self.assertRaises(Exception, self.entry.delete) # make sure it shows up in the list, this also fetches the id entry = self.find_time_entry(desc('delete')) self.assertIsNotNone(entry) # delete it entry.delete() # make sure it shows up in the list entry = self.find_time_entry(desc('delete')) self.assertIsNone(entry)
def test_stop_complex(self): # start an entry now self.entry = toggl.TimeEntry(description=desc('stop2')) self.entry.start() # find it entry = self.find_time_entry(desc('stop2')) self.assertIsNotNone(entry) # stop it an hour from now one_hour_ahead = pytz.UTC.localize(datetime.datetime.utcnow() + datetime.timedelta(hours=1)) entry.stop(one_hour_ahead) # find it again entry = self.find_time_entry(desc('stop2')) self.assertIsNotNone(entry) # make sure duration is at least 1 hour (3600 seconds) self.assertGreaterEqual(entry.get('duration'), 3600)
def setUp(self): self.entry = toggl.TimeEntry() # force timezone to be UTC toggl.DateAndTime.tz = pytz.UTC
def deserialize_entries(dicts): '''Deserialize a list of dicts into a list of TimeEntries''' return [toggl.TimeEntry(d) for d in dicts]