def exec_added(entity, config): # type: (Entity, Config) -> bool times = re.compile('\d{1,2}:\d{2}').findall(entity.content) if times: hour, minute = times[0].split(':') target_time = O(entity.due_date_utc) \ .then(lambda d: to_datetime(d, config.timezone)) \ .or_(datetime.now(timezone(config.timezone))) begin_time = minus3h(target_time).replace(hour=int(hour), minute=int(minute), second=0) r = api.add_reminder( config.todoist.api_token, entity.id, begin_time - timedelta(minutes=config.remind.minutes_delta)) if not r: return False api.notify_slack( config.message_format_by_event[entity.event].format( **entity.to_dict()), config) return True
def exec_completed(entity, config): # type: (Entity, Config) -> bool # Ignore the task which is child task and closed in past if entity.in_history and entity.parent_id: return True special_event = config.special_events.find_by_id(entity.id) # type: Event if special_event: # TODO: Create independent function api.notify_slack(py_.sample(special_event.messages), config) if special_event == config.special_events.leave_work: scheduled, interrupted = create_daily_report( config) # type: TList[TaskReport] report_message = """ :spiral_calendar_pad: *朝に予定したタスク* `{total_scheduled}時間 (予定: {scheduled_estimate}時間)` {scheduled} :ikiro: *割り込みで入ったタスク* `{total_interrupted}時間` {interrupted} """.format( total_scheduled=round(scheduled.sum_by(_.elapsed) / 60.0, 1), scheduled_estimate=round( scheduled.filter(_.estimate).sum_by(_.estimate) / 60.0, 1), total_interrupted=round( interrupted.sum_by(_.elapsed) / 60.0, 1), scheduled=scheduled.map(lambda r: to_report_string( r, config.daily_report_format)).join("\n"), interrupted=interrupted.map(lambda r: to_report_string( r, config.daily_report_format)).join("\n"), ) api.notify_slack(report_message, config) elif special_event == config.special_events.start_work: scheduled_tasks = api.fetch_uncompleted_tasks(config.todoist.api_token) \ .filter(lambda x: equal_now_day(x.due_date_utc, config.timezone)) \ .filter(lambda x: is_valid_project(config, x.project_id) or in_special_events(config, x.id) ) \ .order_by(_.day_order) \ .order_by(_.priority, reverse=True) """:type: TList[TodoistApiTask]""" S3.put_object( Bucket=BUCKET, Key=SCHEDULED_TASKS, Body=scheduled_tasks.reject( lambda x: in_special_events(config, x.id)).to_json()) api.notify_slack( scheduled_tasks.map(lambda x: config.morning_report_format.base.format( name=x.content, project_name=O(config.project_by_id.get(x.project_id)).then(_.name).or_("節目"), estimate_label=O( config.special_labels.estimates.find(lambda l: l.id in x.labels) ).then(_.name).or_('----') )) \ .join("\n"), config ) elif special_event == config.special_events.start_make_schedule: earliers, laters = api.fetch_uncompleted_tasks(config.todoist.api_token) \ .filter(lambda x: equal_now_day(x.due_date_utc, config.timezone)) \ .reject(lambda x: x.id in config.daily_default_order) \ .partial(lambda x: O(config.project_by_id.get(x.project_id)).then_or_none(_.order_earlier)) """:type: TList[TodoistApiTask]""" api.update_day_orders( config.todoist.api_token, earliers.map(_.id) + config.daily_default_order + laters.map(_.id)) else: next_item = config.next_message_format and fetch_next_item(config) next_message = config.next_message_format.format( **next_item) if next_item is not None else '' message = config.message_format_by_event[entity.event].format( **entity.to_dict()) + '\n' + next_message api.notify_slack(message, config) # Toggl action current_entry = api.access_toggl('/time_entries/current', config.toggl.api_token).json()['data'] if not current_entry or current_entry[ 'description'] != entity.content_without_emoji: return True api.access_toggl('/time_entries/{}/stop'.format(current_entry['id']), config.toggl.api_token) return True
def test_then_not_none(self): assert O(1).then(lambda x: x + 1).or_(-1) == 2
def create_daily_report(config): # type: (Config) -> Tuple[TList[TaskReport], TList[TaskReport]] # todoist completed_todoist_reports = api.fetch_completed_tasks( config.todoist.api_token, minus3h(now(config.timezone)).replace(hour=0, minute=0, second=0) ).map(lambda x: TaskReport.from_dict({ "id": x["task_id"], "name": x["content"].split(" @")[0], "project_id": x["project_id"], "project_name": O(config.project_by_id.get(x["project_id"])).then(_.name).or_("なし"), "is_waiting": config.special_labels.waiting.name in x["content"].split(" @")[1:], "completed_date": x["completed_date"], "estimate": O( config.special_labels.estimates.find(lambda l: l.name in x[ "content"].split(" @")[1:])).then_or_none(_.value) })).filter(lambda x: is_measured_project(config, x.project_id)) """:type: TList[TaskReport]""" uncompleted_todoist_reports = api.fetch_uncompleted_tasks(config.todoist.api_token) \ .map(lambda x: TaskReport.from_dict({ "id": x.id, "name": x.content, "project_id": x.project_id, "project_name": O(config.project_by_id.get(x.project_id)).then(_.name).or_("なし"), "is_waiting": config.special_labels.waiting.id in x.labels, "due_date_utc": x.due_date_utc, "estimate": O( config.special_labels.estimates.find(lambda l: l.id in x.labels) ).then_or_none(_.value) })).filter(lambda x: is_measured_project(config, x.project_id)) """:type: TList[TaskReport]""" # toggl toggl_reports = api.fetch_reports(workspace_id=config.toggl.workspace, since=minus3h(now(config.timezone)), api_token=config.toggl.api_token) \ .group_by(_.task_uniq_key) \ .to_values() \ .map(lambda xs: toggl_to_task_report(config, xs, completed_todoist_reports + uncompleted_todoist_reports)) """:type: TList[TaskReport]""" work_start_date = completed_todoist_reports \ .find(_.id == config.special_events.start_work.id) \ .completed_date """:type: datetime""" # --- s3_obj = S3.get_object(Bucket=BUCKET, Key=SCHEDULED_TASKS) # The task completed before scheduling is the scheduled done_before_scheduled = toggl_reports \ .filter(lambda x: completed_todoist_reports.find(_.id == x.id)) \ .filter(lambda x: completed_todoist_reports.find(_.id == x.id).completed_date <= work_start_date) """:type: TList[TaskReport]""" scheduled_reports = TodoistApiTask.from_json_to_list(s3_obj['Body'].read()) \ .map(lambda x: TaskReport.from_dict({ "id": x.id, "name": x.content, "project_id": x.project_id, "project_name": O(config.project_by_id.get(x.project_id)).then(_.name).or_("なし"), "is_waiting": config.special_labels.waiting.id in x.labels, "elapsed": O(toggl_reports.find(_.id == x.id)).then(_.elapsed).or_(0), "due_date_utc": x.due_date_utc, "estimate": O( config.special_labels.estimates.find(lambda l: l.id in x.labels) ).then_or_none(_.value), "status": Status.COMPLETED if x.id in completed_todoist_reports.map(_.id) \ else Status.REMOVED if not x.id in uncompleted_todoist_reports.map(_.id) \ else Status.RE_SCHEDULED if to_datetime(x.due_date_utc, config.timezone).day != minus3h(now(config.timezone)).day \ else Status.WAITING if config.special_labels.waiting.id in x.labels \ else Status.NOT_STARTED_YET if O(toggl_reports.find(_.id == x.id)).then(_.elapsed).or_(0) == 0 \ else Status.IN_PROGRESS })) \ .filter(lambda x: is_measured_project(config, x.project_id)) """:type: TList[TaskReport]""" measured_interrupted_reports = toggl_reports \ .reject(lambda x: x.id in (scheduled_reports + done_before_scheduled).map(_.id)) """:type: TList[TaskReport]""" unmeasured_interrupted_reports = uncompleted_todoist_reports \ .filter(lambda x: equal_now_day(x.due_date_utc, config.timezone)) \ .reject(lambda x: x.id in measured_interrupted_reports.map(_.id)) \ .reject(lambda x: x.id in (scheduled_reports + done_before_scheduled).map(_.id)) """:type: TList[TaskReport]""" interrupted_reports = (measured_interrupted_reports + unmeasured_interrupted_reports) \ .map(lambda x: TaskReport.from_dict({ "id": x.id, "name": x.name, "project_id": x.project_id, "project_name": x.project_name, "is_waiting": x.is_waiting, "elapsed": x.elapsed, "completed_date": x.completed_date, "due_date_utc": x.due_date_utc, "estimate": x.estimate, "status": Status.COMPLETED if x.id in completed_todoist_reports.map(_.id) \ else Status.COMPLETED if not x.id in uncompleted_todoist_reports.map(_.id) \ else Status.WAITING if x.is_waiting \ else Status.NOT_STARTED_YET if x.elapsed == 0 \ else Status.IN_PROGRESS })) return scheduled_reports, interrupted_reports
def test_then_or_none_empty_list(self): assert O([]).then_or_none(lambda x: x) == []
def test_then_or_none_empty_dict(self): assert O({}).then_or_none(lambda x: x) == {}
def test_then_or_none_zero(self): assert O(0).then_or_none(lambda x: x) == 0
def test_then_or_none_none(self): assert O(None).then_or_none(lambda x: x + 1) is None
def test_then_or_none_not_none(self): assert O(1).then_or_none(lambda x: x + 1) == 2
def test_then_empty_dict(self): assert O({}).then(lambda x: x).or_("hoge") == {}
def test_then_empty_list(self): assert O([]).then(lambda x: x).or_("hoge") == []
def test_then_none(self): assert O(None).then(lambda x: x + 1).or_("hoge") == "hoge"
def test_then_zero(self): assert O(0).then(lambda x: x).or_(-1) == 0