Beispiel #1
0
def add_tasks_todoist(project_name: str, tasks: List[Task]):
    # Authenticate with the todoist API
    api_token = open("todoist_token.txt").read()
    api = TodoistAPI(api_token)
    api.sync()

    # Find the uni project or create it
    projects = api.state["projects"]
    projects = list(filter(lambda p: p["name"] == project_name, projects))
    if len(projects) != 0:
        project_id = projects[0]["id"]
    else:
        procject = api.projects.add(project_name)
        api.commit()
        project_id = procject["id"]

    # Add the tasks
    for task in tasks:
        api.items.add(
            task.text,
            project_id=project_id,
            due={"date": task.duedate.strftime("%Y-%m-%d")},
        )
        print(task)
    api.commit()
Beispiel #2
0
class Todoist:
    def __init__(self):
        self.token = token
        self.api = TodoistAPI(self.token)
        self.api.sync()

    def sync(self):
        self.api.sync()

    def get_obj(self, obj):
        if obj == 'state':
            return self.api.state
        elif obj == 'projects':
            return self.api.state['projects']
        elif obj == 'labels':
            return self.api.state['labels']
        elif obj == 'items':
            return self.api.state['items']
        elif obj == 'sections':
            return self.api.state['sections']
        else:
            return 'Object Not Found'

    def get_items(self, p_id=False):
        all_items = self.get_obj('items')
        if p_id:
            select_items = []
            for item in all_items:
                if item['project_id'] == p_id:
                    select_items.append(item)
            all_items = select_items
        return all_items
def test_add_indox_task():
    g.db.close()
    content = 'Это новая задача!'

    debug_processing('{"type": "message_new", '
                     '"object": {"id": 43, "date": 1492522323, "out": 0, '
                     '"user_id": 481116745, "read_state": 0, '
                     '"body": "' + content + '"}}')

    acc = Subscription.get(Subscription.messenger_user_id == 481116745).account

    service = TodoistService()
    api = TodoistAPI(AccessToken.get(account=acc.id).token)
    api.sync()

    found_content = [
        item['content'] for item in api.items.all()
        if item['content'] == content and item['project_id'] ==
        service.project_name_to_id(api=api, proj_name='Inbox')
    ][0]

    assertEqual(funcname=test_add_indox_task.__name__,
                a=found_content,
                b=content)
    # assertEqual(vkapi.sended_message, 'Я не понял команды. Попробуйте еще раз.', __name__)

    service.delete_task(task=content, proj='Inbox', user_id=481116745)
class TaskManager:
    def __init__(self, settings):
        self.todoist = TodoistAPI(settings['API_KEY'])
        self.todoist.sync()
        self.user = self.todoist.state['user']['id']
        self.labels = convert_labels(self.todoist.state['labels'])

    def _sync(func):
        def synced(self, *args, **kwargs):
            self.todoist.sync()
            return func(self, *args, **kwargs)
        return synced

    @_sync
    def avg_spoons(self):
        completed_spoonsy_tasks = [ task for task in self.get_tasks() if is_spoonsy_task(task, self.labels) and task['date_completed'] ]
        return mean([ calculate_values(task['labels']) for task in completed_spoonsy_tasks ])

    @_sync
    def get_projects(self):
        return [ project for project in self.todoist.state['projects'] if project_owned_by_user(project, self.user) ]

    @_sync
    def get_tasks(self, project_name='all'):
        all_tasks = self.todoist.state['items']
        if project_name == 'all':
            return all_tasks
        else:
            project = get_project_or_none(all_tasks, project_name)
            return [ item for item in all_tasks if item['project_id'] == project['id'] ]
Beispiel #5
0
def initiate_api(access_token):
    """Initiate and sync Todoist API"""
    api = TodoistAPI(access_token)
    api.sync()
    if bool(api['user']):
        return api
    else:
        return None
def add_task(task,
             project='',
             date_string='',
             accuracy='day',
             due_datetime=''):
    g.db.close()

    import time
    time.sleep(1)
    body = '{0}{1}{2}.'.format(task, ' в ' + project if project != '' else '',
                               '. ' + date_string if datetime != '' else '')
    debug_processing('{"type": "message_new", '
                     '"object": {"id": 43, "date": 1492522323, "out": 0, '
                     '"user_id": 481116745, "read_state": 0, '
                     '"body": "' + body + '"}}')

    time.sleep(1)

    if 'Все верно?' in vkapi.sended_message:  # bad
        debug_processing('{"type": "message_new", '
                         '"object": {"id": 43, "date": 1492522323, "out": 0, '
                         '"user_id": 481116745, "read_state": 0, '
                         '"body": "Ага!"}}')

    time.sleep(1)
    acc = Subscription.get(Subscription.messenger_user_id == 481116745).account

    service = TodoistService()
    api = TodoistAPI(AccessToken.get(account=acc.id).token)
    api.sync()

    project = 'Inbox' if project == '' else project
    try:
        found_content = [
            item for item in api.items.all()
            if item['content'] == task + '.' and item['project_id'] ==
            service.project_name_to_id(api=api, proj_name=project)
        ][0]
    except Exception as e:
        print(api.items.all())

        found_content = [
            item for item in api.items.all()
            if item['content'] == task + '.' and item['project_id'] ==
            service.project_name_to_id(api=api, proj_name=project)
        ][0]

    assertEqual(funcname=add_task.__name__,
                a=found_content.data['content'],
                b=task + '.')

    # import datetime as dt
    # timedelts = {'day': dt.timedelta(days=1), 'hour': dt.timedelta(hours=1)}
    # assertTrue(due_datetime < found_content['due_date_utc'] + timedelts[accuracy]
    #            and due_datetime > found_content['due_date_utc'] - timedelts[accuracy], funcname=add_task.__name__)

    service.delete_task(task=task + '.', proj=project, user_id=481116745)
def todoist_reading_list(handler=None):
    todoist = TodoistAPI(get_credential('todoist_token'))
    todoist.sync()

    reading_list = t_utils.get_project_by_name(todoist, 'reading list')
    categories = t_utils.get_child_projects(todoist, reading_list)

    for task in todoist.state['items']:
        for project in categories:
            if task['project_id'] == project['id']:
                content = task['content']
                logging.info(content)
                m = re.search(r'\[([^\[]+)\]\((.*)\)', content)

                # The todoist app stores links as either a markdown formatted link or "title - url"
                # if markdown links fail, try to parse the "title - url" format.
                if m:
                    logging.info("markdown group")
                    title = m.group(1)
                    url = m.group(2)
                    logging.info(title)
                    logging.info(url)
                else:
                    logging.info("hyphen group")
                    content_components = content.split(" - ")
                    if len(content_components) > 1:
                        title = ''.join(content_components[:-1])
                        url = content_components[-1].strip()
                        logging.info(title)
                        logging.info(url)
                    else:
                        task.update(content="FAILED TO PARSE: " + content)
                        task.move(parent_id=reading_list['id'])

                comments = t_utils.get_comments_for_task(todoist, task)

                article = article_parser.parse_url(url)

                data = {
                    'url': url,
                    'title': title,
                    'summary': article.summary,
                    'keywords': article.keywords,
                    'text': article.text,
                    'published_date': article.publish_date,
                    'notes': comments,
                    'category': project['name']
                }

                handler.publish('archive_article', url, data)

                task.complete()

    todoist.commit()
Beispiel #8
0
def openTodoist():
    f = open('config.json',) 
    config = json.load(f)
    f.close()

    if config['api-token']=='':
        print('Please add an API token to config.json')
        exit()

    api = TodoistAPI(token=config['api-token'])
    api.sync()
    return api
Beispiel #9
0
class TodoistWrapper:
    def __init__(self, conf):
        self.todoist = TodoistAPI(conf['secret'])
        self.conf = conf
        self.todoist.sync()

    def get_projects(self):
        projs = [Project(self, p['id']) for p in self.todoist['projects']]
        if not self.conf['show_inbox']:
            return [proj for proj in projs if proj.name != 'Inbox']
        else:
            return projs

    def project_data(self, project_id):
        return self.todoist.projects.get_data(project_id)

    def task_data(self, task_id):
        return self.todoist.items.get(task_id)

    def create_task(self, name, project_id):
        self.todoist.items.add(name, project_id)
        self.todoist.commit()

    def create_project(self, project_name):
        self.todoist.projects.add(project_name)
        self.todoist.commit()

    def complete_task(self, task_id):
        try:
            self.todoist.items.complete([int(task_id)])
            self.todoist.commit()
        except ValueError:
            raise CmdError("Argument must be a task id.")

    def _get_project_task_ids(self, project_id):
        try:
            project = Project(self, int(project_id))
            task_ids = [task.obj_id for task in project]
            return task_ids
        except ValueError:
            raise CmdError("Argument must be a project id.")

    def complete_project(self, project_id):
        self.todoist.items.complete(self._get_project_task_ids(project_id))
        self.todoist.commit()

    def clear_project(self, project_id):
        self.todoist.items.delete(self._get_project_task_ids(project_id))
        self.todoist.commit()

    def delete_project(self, project_id):
        self.todoist.projects.delete([project_id])
        self.todoist.commit()
def add_issue_to_todoist(event=None, handler=None):
    todoist = TodoistAPI(get_credential('todoist_token'))
    todoist.sync()

    issue_project = t_utils.get_project_by_name(todoist, 'issues')

    r = event.data
    todoist.items.add(
        '{} [#{}]({})'.format(r['title'], r['number'], r['html_url']),
        project_id=issue_project['id'],
    )

    # todo close todos for prs/issues that are no longer active
    todoist.commit()
Beispiel #11
0
def add_mention_to_todoist(event=None, handler=None):
    todoist = TodoistAPI(get_credential('todoist_token'))
    todoist.sync()

    mention_project = t_utils.get_project_by_name(todoist, 'GH Mentions')

    r = event.data
    todoist.items.add(
        'GH Mention - {} [#{}]({})'.format(r['title'], r['number'], r['url']),
        # project_id=mention_project['id'],
        # auto_reminder=True,
        # due={"string": "next workday at 9am"},
        priority=4)

    # todo close todos for prs/issues that are no longer active
    todoist.commit()
Beispiel #12
0
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    tg_id = db.Column(db.BigInteger, unique=True)
    first_name = db.Column(db.String(255))
    last_name = db.Column(db.String(255))
    username = db.Column(db.String(100))
    todoist_id = db.Column(db.BigInteger, nullable=True, index=True)
    state = db.Column(db.String(36), default='', index=True)
    auth = db.Column(db.String(255), default='')
    is_active = db.Column(db.Boolean, default=True)
    created_at = db.Column(db.DateTime, nullable=False)
    last_active_at = db.Column(db.DateTime, nullable=True)

    # noinspection PyTypeChecker
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.update = None  # type: Update
        self.message = None  # type: Message
        self.api = None  # type: TodoistAPI
        self.now = None  # type: datetime

    def is_authorized(self):
        return bool(self.auth)

    def first_init_api(self):
        self.init_api()
        if not self.is_authorized():
            return
        from app.telegram.handlers import bot
        bot().base_welcome(self)

    def init_api(self):
        if not self.is_authorized():
            return
        try:
            with app.app_context():
                self.api = TodoistAPI(self.auth,
                                      cache=app.config['TODOIST']['CACHE'])
            result = self.api.sync()
            if 'error' in result:
                raise SyncError
            if 'user' in result and not self.todoist_id:
                self.todoist_id = result['user']['id']
        except SyncError:
            self.auth = ''
            self.todoist_id = None
            self.api = None

    def send_message(self, text, **kwargs):
        from app.telegram.handlers import bot
        try:
            return bot().send_message(self.tg_id, text, **kwargs)
        except Unauthorized:
            self.is_active = False
            db.session.add(self)
            db.session.commit()
        return False
Beispiel #13
0
def add_issue_to_todoist(event=None, handler=None):
    todoist = TodoistAPI(get_credential('todoist_token'))
    todoist.sync()

    issue_project = None

    for p in todoist.state['projects']:
        if p['name'].lower() == 'issues':
            issue_project = p
            break
    
    r = event.data
    todoist.items.add(
        '{} [#{}]({})'.format(r.title, r.number , r.html_url),
        project_id=issue_project['id'],
    )

    # todo close todos for prs/issues that are no longer active
    todoist.commit()
Beispiel #14
0
def add_pr_to_todoist(event=None, handler=None):
    todoist = TodoistAPI(get_credential('todoist_token'))
    todoist.sync()

    review_project = None

    for p in todoist.state['projects']:
        if p['name'].lower() == 'review requests':
            review_project = p
            break

    r = event.data
    todoist.items.add('{} [{} #{}]({})'.format(r.title, r.base.repo.full_name,
                                               r.number, r.html_url),
                      project_id=review_project['id'],
                      auto_reminder=True,
                      due={"string": "next workday at 9am"},
                      priority=4)

    # todo close todos for prs/issues that are no longer active
    todoist.commit()
Beispiel #15
0
class TodoistItems:
    def __init__(self, token):
        self.api_token = token
        self.api = TodoistAPI(self.api_token)
        self.api.sync()

    def key_check(self, item, key):
        try:
            result = item[key]
            return result
        except:
            return False

    def date_check(self, item, date):
        if self.key_check(item, 'due'):
            if item['due']['lang'] == 'ja':
                if date.replace('/', '-') in item['due']['date']:
                    return True
                elif '毎日' in item['due']['string']:
                    return True
            else:
                try:
                    if date == parse(item['due']['date']).strftime('%Y/%m/%d'):
                        return True
                except ValueError:
                    pass
        return False

    def get_project_name(self, item):
        if self.key_check(item, 'project_id'):
            project_id = item['project_id']
        else:
            project_id = item['parent_id']
        project = self.api.projects.get_by_id(project_id)
        return project['name']

    def find_by_date(self, date):
        return [
            item for item in self.api['items'] if self.date_check(item, date)
        ]
Beispiel #16
0
def main(args=None):
    """Console script for greminders2todoist."""

    flow = MyInstalledAppFlow.from_client_secrets_file('client.json', SCOPES)
    creds: Credentials = flow.run_local_server(port=3423)
    api = TodoistAPI(creds.token)
    api.sync()

    tree: _ElementTree = etree.parse(open('./Reminders.html'),
                                     etree.HTMLParser())
    pprint(scan_fields(tree))
    rows = [
        dict(type='task',
             content=task.title,
             priority=4,
             indent=None,
             author=None,
             responsible=None,
             date=proc_date(task),
             date_lang='en',
             timezone=None) for task in gen_tasks(tree)
        if task.state != 'archived' and (
            task.recurrence or task.due and task.due > datetime.now())
    ]
    with open('out.csv', 'w') as outfile:
        writer = csv.DictWriter(outfile, [
            'type', 'content', 'priority', 'indent', 'author', 'responsible',
            'date', 'date_lang', 'timezone'
        ])
        writer.writeheader()
        writer.writerows(rows)

    [inbox] = [p for p in api.state['projects'] if p['name'] == 'Inbox']
    for task in rows:
        api.items.add(task['content'], inbox['id'], date_string=task['date'])
    api.commit()
    return 0
Beispiel #17
0
def add_pr_to_todoist(event=None, handler=None):
    todoist = TodoistAPI(get_credential('todoist_token'))
    todoist.sync()

    # review_project = t_utils.get_project_by_name(todoist, 'review requests')

    r = event.data
    title = 'PR'

    if r['base']['repo']['full_name'] == 'pulp/pulp_ansible':
        title = 'Pulp Ansible PR'

    todoist.items.add(
        '{} - {} [{} #{}]({})'.format(title, r['title'],
                                      r['base']['repo']['full_name'],
                                      r['number'], r['html_url']),
        # project_id=review_project['id'],
        # auto_reminder=True,
        # due={"string": "next workday at 9am"},
        # priority=4
    )

    # todo close todos for prs/issues that are no longer active
    todoist.commit()
Beispiel #18
0
def main():
    with token() as t:
        todo = TodoistAPI(t)
    todo.sync()
    yield todo
    todo.commit()
Beispiel #19
0
class TodoistLibrary:
    api: TodoistAPI

    ### Test Setup ###
    def todoist_api_client_is_connected(self):
        self.api = TodoistAPI('313f6bf203b35e7ac56e39561a80633e459c9c54')
        self.api.sync()

    ### Test Teardown ###
    def test_clean_up(self):
        self.delete_project()
        self.api.commit()

    def commit_and_sync(self):
        self.api.commit()
        self.api.sync()

    def delete_project(self):
        for project in self.api.projects.all():
            self.api.projects.delete(project["id"])
        self.api.items.all().clear()

    def create_project_with_name(self, name: str = None):
        self.api.projects.add(name)

    def create_project_with_name_and_parent_id(self, name: str, parent_id: int = None):
        self.api.projects.add(name=name, parent_id=parent_id)

    def create_task_with_name(self, name_project: str, name_task: str):
        project = self.api.projects.add(name_project)
        self.api.items.add(name_task, project_id=project['id'])

    def create_task_with_name_and_due_date(self, name_project: str, name_task: str, due_date: str):
        project = self.api.projects.add(name_project)
        self.api.items.add(name_task, project_id=project['id'], due={'date': due_date})

    def create_task_and_subtask_with_name(self, name_project: str, name_task: str, name_subtask: str):
        project = self.api.projects.add(name_project)
        task_id = self.api.items.add(name_task, project_id=project['id'], due={'date': '2020-07-18T07:00:00Z'})
        subtask = self.api.items.add(name_subtask, project_id=project['id'], due={'date': '2020-07-18T05:00:00Z'})
        subtask.move(parent_id=task_id['id'])

    def create_project_and_add_comment(self, name_project, name_task):
        project = self.api.projects.add(name_project)
        task_id = self.api.items.add(name_task, project_id=project['id'], due={'date': '2020-07-18T07:00:00Z'})
        self.api.notes.add(task_id['id'], 'Comment3')

    def create_parent_project_and_child_project(self, parent: str, child: str):
        parent_project = self.api.projects.add(parent)
        self.api.projects.add(child, parent_id=parent_project["id"])

    def create_task_with_name_and_priority(self, name_project, task_name, priority: int):
        self.api.projects.add(name_project)
        self.api.items.add(task_name, priority=priority)

    def assert_project_with_name_exists(self, name_project):
        projects = self.api.projects.all(filt=lambda project: project['name'] == name_project)
        assert len(projects) > 0, "Project could not be found"
        project = projects[0]
        assert project['name'] == name_project, "Project is not created"

    def assert_task_exists(self, name_task):
        name_tasks = self.api.items.all(filt=lambda task: task['content'] == name_task)
        assert len(name_tasks) > 0, "Parent task could not be found"
        task = name_tasks[0]
        assert task['content'] == name_task, "Task is not created"


    def assert_task_has_due_date(self, task_name, due_date):
        tasks = self.api.items.all(filt=lambda task: task["content"] == task_name)
        assert len(tasks) > 0, "Parent task could not be found"
        task = tasks[0]
        assert task['due']['date'] == due_date, "Task due date is wrong"

    def assert_task_with_subtask_exists(self, parent_task, child_task):
        parent_tasks = self.api.items.all(filt=lambda task: task["content"] == parent_task)
        assert len(parent_tasks) > 0, "Parent task could not be found"
        parent = parent_tasks[0]
        child_tasks = self.api.items.all(filt=lambda project: project["content"] == child_task)
        assert len( child_tasks) > 0, "Child task could not be found"
        child = child_tasks[0]
        assert child["parent_id"] == parent['id'], "Task is not parent of another project"

    def assert_project_is_parent_of_another_project(self, parent, child):
        projects = self.api.projects.all(filt=lambda project: project["name"] == parent)
        assert len(projects) > 0, "Parent project could not be found"
        parent_project = projects[0]
        projects = self.api.projects.all(filt=lambda project: project["name"] == child)
        assert len(projects) > 0, "Child project could not be found"
        child_project = projects[0]
        assert child_project["parent_id"] == parent_project['id'], "Project is not parent of another project"

    def commit_and_sync_expect_error(self, error: str, error_code: int = None):
        try:
            self.api.commit()
            self.api.sync()
            assert False, "Server did not throw any error"
        except AssertionError as ae:
            raise ae
        except SyncError as ex:
            assert ex.args[1]['error'] == error, "Wrong error message"
            if error_code is not None and ex.args[1]['error_code'] != error_code:
                assert False, "Wrong error code"

    def task_has_priority(self, task_name, priority: int):
        tasks = self.api.items.all(filt=lambda task: task["content"] == task_name)
        assert len(tasks) > 0, "Task could not be found"
        task = tasks[0]
        assert task['priority'] == priority, "Task priority is wrong"
Beispiel #20
0
import config
import urwid
from todoist import TodoistAPI
import pprint

token = config.todoist_api_key
api = TodoistAPI(token)
api.sync()
items = api.state['items']

pprint.pprint(len(items))

for item in items:
    # pprint.pprint(item, indent=4)
    # print('++++++++++++++++++++++++++++++++++')
    pprint.pprint('Content: ' + item['content'], indent=4)
    pprint.pprint('Date Completed: ' + str(item['date_completed']), indent=4)
    pprint.pprint('Checked: ' + str(item['checked']), indent=4)
    # pprint.pprint('Due Date: ' + item['due']['date'], indent=4)
    print(
        '////////////////////////////////////////////////////////////////////////////////////\n'
    )


def exit_on_q(key):
    if key in ('q', 'Q'):
        raise urwid.ExitMainLoop()


palette1 = [('tl_txt_pal', 'light magenta', 'black'),
            ('tr_txt_pal', 'light blue', 'black'),
Beispiel #21
0
class Todoist(SingletonMixin):
    """A wrapper for the Todoist API."""

    def __init__(self, api_token: str) -> None:
        super().__init__()
        self.api = TodoistAPI(api_token)
        self.data: JsonDict = {}
        self.projects: DictProjectId = {}
        self._allow_creation = False

    def smart_sync(self):
        """Only perform a full resync if needed."""
        if not self.data.get("projects", {}):
            # If internal data has no projects, reset the state and a full (slow) sync will be performed.
            self.api.reset_state()

        partial_data = {}
        for attempt in range(3):
            # For some reason, sometimes this sync() method returns an empty string instead of a dict.
            # In this case, let's try again for a few times until we get a dictionary.
            partial_data = self.api.sync()
            if isinstance(partial_data, dict):
                break
            LOGGER.warning(f"Retrying, attempt {attempt + 1}: partial_data is not a dict(): {partial_data!r}")

        self._merge_new_data(partial_data)

        self.projects = dict(PROJECTS_NAME_ID_JMEX.search(self.data))

    def _merge_new_data(self, partial_data: JsonDict):
        if not self.data:
            self.data = partial_data
            return

        for key, value in partial_data.items():
            if isinstance(value, list):
                if key not in self.data:
                    self.data[key] = []
                self.data[key].extend(value)
            elif isinstance(value, dict):
                if key not in self.data:
                    self.data[key] = {}
                self.data[key].update(value)
            else:
                self.data[key] = value

    def keys(self):
        """Keys of the data."""
        return sorted(self.data.keys())

    @deprecated(reason="use find* functions instead")
    def fetch(
        self,
        element_name: str,
        return_field: str = None,
        filters: JsonDict = None,
        index: int = None,
        matching_function=all,
    ) -> List[Any]:
        """Fetch elements matching items that satisfy the desired parameters.

        :param element_name: Name of the element to search. E.g. 'projects', 'items'.
        :param return_field: Name of the return field. If None, return the whole element.
        :param filters: Parameters for the search.
        :param index: Desired index to be returned. If nothing was found, return None.
        :param matching_function: ``all`` items by default, but ``any`` can be used as well.
        """
        if not filters:
            values_to_list: JsonDict = {}
        else:
            values_to_list = {key: [value] if not isinstance(value, list) else value for key, value in filters.items()}
        found_elements = [
            element[return_field] if return_field else element
            for element in self.data[element_name]
            if not filters or matching_function(element[key] in value for key, value in values_to_list.items())
        ]
        if index is not None:
            return found_elements[index] if found_elements else None
        return found_elements

    @deprecated(reason="use find* functions instead")
    def fetch_first(self, element_name: str, return_field: str = None, filters: JsonDict = None) -> Optional[Any]:
        """Fetch only the first result from the fetched list, or None if the list is empty."""
        return self.fetch(element_name, return_field, filters, 0)

    def find_project_id(self, exact_name: str) -> Optional[int]:
        """Find a project ID by its exact name.

        :param exact_name: Exact name of a project.
        """
        return self.projects.get(exact_name, None)

    def find_projects(self, partial_name: str = "") -> DictProjectId:
        """Find projects by partial name.

        :param partial_name: Partial name of a project.
        """
        return {
            name: project_id for name, project_id in self.projects.items() if partial_name.casefold() in name.casefold()
        }

    def find_project_items(self, exact_project_name: str, extra_jmes_expression: str = "") -> List[JsonDict]:
        """Fetch all project items by the exact project name.

        :param exact_project_name: Exact name of a project.
        :param extra_jmes_expression: Extra JMESPath expression to filter fields, for instance.
        """
        project_id = self.find_project_id(exact_project_name)
        if not project_id:
            return []
        return jmespath.search(f"items[?project_id==`{project_id}`]{extra_jmes_expression}", self.data)

    def find_items_by_content(self, exact_project_name: str, partial_content: str) -> List[JsonDict]:
        """Return items of a project by partial content.

        :param exact_project_name: Exact name of a project.
        :param partial_content: Partial content of an item.
        """
        clean_content = partial_content.casefold()
        return [
            item
            for item in self.find_project_items(exact_project_name)
            if clean_content in item.get("content", "").casefold()
        ]
Beispiel #22
0
class ServiceTodoist(ServicesMgr):

    def __init__(self, token=None, **kwargs):
        super(ServiceTodoist, self).__init__(token, **kwargs)
        self.AUTH_URL = 'https://todoist.com/oauth/authorize'
        self.ACC_TOKEN = 'https://todoist.com/oauth/access_token'
        self.REQ_TOKEN = 'https://todoist.com/oauth/access_token'
        self.consumer_key = settings.TH_TODOIST['client_id']
        self.consumer_secret = settings.TH_TODOIST['client_secret']
        self.scope = 'task:add,data:read,data:read_write'
        self.service = 'ServiceTodoist'
        self.oauth = 'oauth2'
        if token:
            self.token = token
            self.todoist = TodoistAPI(token)

    def read_data(self, **kwargs):
        """
            get the data from the service
            as the pocket service does not have any date
            in its API linked to the note,
            add the triggered date to the dict data
            thus the service will be triggered when data will be found

            :param kwargs: contain keyword args : trigger_id at least
            :type kwargs: dict

            :rtype: list
        """
        trigger_id = kwargs.get('trigger_id')
        date_triggered = kwargs.get('date_triggered')
        data = []
        project_name = 'Main Project'
        items = self.todoist.sync()
        for item in items.get('items'):
            date_added = arrow.get(item.get('date_added'),
                                   'ddd DD MMM YYYY HH:mm:ss ZZ')
            if date_added > date_triggered:
                for project in items.get('projects'):
                    if item.get('project_id') == project.get('id'):
                        project_name = project.get('name')
                data.append({'title': "From TodoIst Project {0}"
                                      ":".format(project_name),
                             'content': item.get('content')})
        cache.set('th_todoist_' + str(trigger_id), data)
        return data

    def save_data(self, trigger_id, **data):
        """
            let's save the data
            :param trigger_id: trigger ID from which to save data
            :param data: the data to check to be used and save
            :type trigger_id: int
            :type data:  dict
            :return: the status of the save statement
            :rtype: boolean
        """
        kwargs = {}

        title, content = super(ServiceTodoist, self).save_data(trigger_id,
                                                               data, **kwargs)

        if self.token:
            if title or content or \
                            (data.get('link') and len(data.get('link'))) > 0:
                content = title + ' ' + content + ' ' + data.get('link')

                self.todoist.add_item(content)

                sentence = str('todoist {} created').format(data.get('link'))
                logger.debug(sentence)
                status = True
            else:
                status = False
        else:
            logger.critical("no token or link provided for "
                            "trigger ID {} ".format(trigger_id))
            status = False
        return status
Beispiel #23
0
 def _api_for_user(self, user_id):
     acc = Subscription.get(
         Subscription.messenger_user_id == user_id).account
     api = TodoistAPI(AccessToken.get(AccessToken.account == acc).token)
     api.sync()
     return api
Beispiel #24
0
class Todoist(SingletonMixin):
    """A wrapper for the Todoist API."""
    def __init__(self, api_token: str) -> None:
        super().__init__()
        self.api = TodoistAPI(api_token)
        self.data: JsonDict = {}
        self.projects: DictProjectId = {}
        self._allow_creation = False

    def smart_sync(self):
        """Only perform a full resync if needed."""
        if not self.data.get("projects", {}):
            # If internal data has no projects, reset the state and a full (slow) sync will be performed.
            self.api.reset_state()

        partial_data = {}
        for attempt in range(3):
            # For some reason, sometimes this sync() method returns an empty string instead of a dict.
            # In this case, let's try again for a few times until we get a dictionary.
            partial_data = self.api.sync()
            if isinstance(partial_data, dict):
                break
            LOGGER.warning(
                f"Retrying, attempt {attempt + 1}: partial_data is not a dict(): {partial_data!r}"
            )

        self._merge_new_data(partial_data)

        self.projects = dict(PROJECTS_NAME_ID_JMEX.search(self.data))

    def _merge_new_data(self, partial_data: JsonDict):
        if not self.data:
            self.data = partial_data
            return

        for key, value in partial_data.items():
            if isinstance(value, list):
                if key not in self.data:
                    self.data[key] = []
                self.data[key].extend(value)
            elif isinstance(value, dict):
                if key not in self.data:
                    self.data[key] = {}
                self.data[key].update(value)
            else:
                self.data[key] = value

    def keys(self):
        """Keys of the data."""
        return sorted(self.data.keys())

    @deprecated(reason="use find* functions instead")
    def fetch(
        self,
        element_name: str,
        return_field: str = None,
        filters: JsonDict = None,
        index: int = None,
        matching_function=all,
    ) -> List[Any]:
        """Fetch elements matching items that satisfy the desired parameters.

        :param element_name: Name of the element to search. E.g. 'projects', 'items'.
        :param return_field: Name of the return field. If None, return the whole element.
        :param filters: Parameters for the search.
        :param index: Desired index to be returned. If nothing was found, return None.
        :param matching_function: ``all`` items by default, but ``any`` can be used as well.
        """
        if not filters:
            values_to_list: JsonDict = {}
        else:
            values_to_list = {
                key: [value] if not isinstance(value, list) else value
                for key, value in filters.items()
            }
        found_elements = [
            element[return_field] if return_field else element
            for element in self.data[element_name]
            if not filters or matching_function(
                element[key] in value for key, value in values_to_list.items())
        ]
        if index is not None:
            return found_elements[index] if found_elements else None
        return found_elements

    @deprecated(reason="use find* functions instead")
    def fetch_first(self,
                    element_name: str,
                    return_field: str = None,
                    filters: JsonDict = None) -> Optional[Any]:
        """Fetch only the first result from the fetched list, or None if the list is empty."""
        return self.fetch(element_name, return_field, filters, 0)

    def find_project_id(self, exact_name: str) -> Optional[int]:
        """Find a project ID by its exact name.

        :param exact_name: Exact name of a project.
        """
        return self.projects.get(exact_name, None)

    def find_projects(self, partial_name: str = "") -> DictProjectId:
        """Find projects by partial name.

        :param partial_name: Partial name of a project.
        """
        return {
            name: project_id
            for name, project_id in self.projects.items()
            if partial_name.casefold() in name.casefold()
        }

    def find_project_items(self,
                           exact_project_name: str,
                           extra_jmes_expression: str = "") -> List[JsonDict]:
        """Fetch all project items by the exact project name.

        :param exact_project_name: Exact name of a project.
        :param extra_jmes_expression: Extra JMESPath expression to filter fields, for instance.
        """
        project_id = self.find_project_id(exact_project_name)
        if not project_id:
            return []
        return jmespath.search(
            f"items[?project_id==`{project_id}`]{extra_jmes_expression}",
            self.data)

    def find_items_by_content(self, exact_project_name: str,
                              partial_content: str) -> List[JsonDict]:
        """Return items of a project by partial content.

        :param exact_project_name: Exact name of a project.
        :param partial_content: Partial content of an item.
        """
        clean_content = partial_content.casefold()
        return [
            item for item in self.find_project_items(exact_project_name)
            if clean_content in item.get("content", "").casefold()
        ]