Esempio n. 1
0
    def save(self, config=None):  # type: (utils.Config) -> None
        """
        Main method for saving the entity.

        If it is a new entity (eq. entity.id is not set), then calling this method will result in creation of new object using POST call.
        If this is already existing entity, then calling this method will result in updating of the object using PUT call.

        For updating the entity, only changed fields are sent (this is tracked using self.__change_dict__).

        Before the API call validations are performed on the instance and only after successful validation, the call is made.

        :raises exceptions.TogglNotAllowedException: When action (create/update) is not allowed.
        """
        if not self._can_update and self.id is not None:
            raise exceptions.TogglNotAllowedException('Updating this entity is not allowed!')

        if not self._can_create and self.id is None:
            raise exceptions.TogglNotAllowedException('Creating this entity is not allowed!')

        config = config or self._config

        self.validate()

        if self.id is not None:  # Update
            utils.toggl('/{}/{}'.format(self.get_url(), self.id), 'put', self.json(update=True), config=config)
            self.__change_dict__ = {}  # Reset tracking changes
        else:  # Create
            data = utils.toggl('/{}'.format(self.get_url()), 'post', self.json(), config=config)
            self.id = data['data']['id']  # Store the returned ID
Esempio n. 2
0
    def _ids_cleanup(base, config=None, batch=False, *ids):
        config = config or get_config()

        if batch:
            utils.toggl('/{}/{}'.format(base, ','.join([str(eid) for eid in ids])), 'delete', config=config)
        else:
            for entity_id in ids:
                utils.toggl('/{}/{}'.format(base, entity_id), 'delete', config=config)
Esempio n. 3
0
    def _ids_cleanup(base, config=None, batch=False, *ids):
        config = config or get_config()

        if batch:
            utils.toggl('/{}/{}'.format(base,
                                        ','.join([str(eid) for eid in ids])),
                        'delete',
                        config=config)
        else:
            for entity_id in ids:
                utils.toggl('/{}/{}'.format(base, entity_id),
                            'delete',
                            config=config)
Esempio n. 4
0
 def current_user(self, config=None):  # type: (utils.Config) -> 'User'
     """
     Fetches details about the current user.
     """
     fetched_entity = utils.toggl('/me', 'get', config=config)
     return self.entity_cls.deserialize(config=config,
                                        **fetched_entity['data'])
Esempio n. 5
0
    def signup(cls, email, password, timezone=None, created_with='TogglCLI',
               config=None):  # type: (str, str, str, str, utils.Config) -> User
        """
        Creates brand new user. After creation confirmation email is sent to him.

        :param email: Valid email of the new user.
        :param password: Password of the new user.
        :param timezone: Timezone to be associated with the user. If empty, than timezone from config is used.
        :param created_with: Name of application that created the user.
        :param config:
        :return:
        """
        if config is None:
            config = utils.Config.factory()

        if timezone is None:
            timezone = config.timezone

        if not validate_email(email):
            raise exceptions.TogglValidationException('Supplied email \'{}\' is not valid email!'.format(email))

        user_json = json.dumps({'user': {
            'email': email,
            'password': password,
            'timezone': timezone,
            'created_with': created_with
        }})
        data = utils.toggl("/signups", "post", user_json, config=config)
        return cls.deserialize(config=config, **data['data'])
Esempio n. 6
0
    def delete(self, config=None):  # type: (utils.Config) -> None
        """
        Method for deletion of the entity through API using DELETE call.

        This will not delete the instance's object in Python, therefore calling save() method after deletion will
        result in new object created using POST call.

        :raises exceptions.TogglNotAllowedException: When action is not allowed.
        """
        if not self._can_delete:
            raise exceptions.TogglNotAllowedException('Deleting this entity is not allowed!')

        if not self.id:
            raise exceptions.TogglException('This instance has not been saved yet!')

        utils.toggl('/{}/{}'.format(self.get_url(), self.id), 'delete', config=config or self._config)
        self.id = None  # Invalidate the object, so when save() is called after delete a new object is created
Esempio n. 7
0
    def all_from_reports(
        self,
        start=None,
        stop=None,
        workspace=None,
        config=None
    ):  # type: (typing.Optional[datetime_type], typing.Optional[datetime_type], typing.Union[str, int, Workspace], typing.Optional[utils.Config]) -> typing.Generator[TimeEntry, None, None]
        """
        Method that implements fetching of all time entries through Report API.
        No limitation on number of time entries.

        :param start: From when time entries should be fetched. Defaults to today - 6 days.
        :param stop: Until when time entries should be fetched. Defaults to today, unless since is in future or more than year ago, in this case until is since + 6 days.
        :param workspace: Workspace from where should be the time entries fetched from. Defaults to Config.default_workspace.
        :param config:
        :return: Generator that yields TimeEntry
        """
        from .. import toggl

        config = config or utils.Config.factory()
        page = 1

        if workspace is None:
            wid = config.default_workspace.id
        elif isinstance(workspace, Workspace):
            wid = workspace.id
        elif isinstance(workspace, int):
            wid = workspace
        else:
            try:
                wid = int(workspace)
            except (ValueError, TypeError):
                logger.exception(
                    "Couldn't infer workspace, falling back to default")
                wid = config.default_workspace.id

        while True:
            url = self._build_reports_url(start, stop, page, wid)
            returned = utils.toggl(url,
                                   'get',
                                   config=config,
                                   address=toggl.REPORTS_URL)

            if not returned.get('data'):
                return

            for entity in returned.get('data'):
                yield self._deserialize_from_reports(config, entity, wid)

            if not self._should_fetch_more(page, returned):
                return

            page += 1
Esempio n. 8
0
    def get(self,
            id=None,
            config=None,
            **conditions
            ):  # type: (typing.Any, utils.Config, **typing.Any) -> Entity
        """
        Method for fetching detail object of the entity. it fetches the object based on specified conditions.

        If ID is used then detail URL is used to fetch object.
        If other conditions are used to specify the object, then TogglSet will fetch all objects using listing URL and
        filter out objects based on passed conditions.

        In any case result must be only one object or no object at all. Returned is the fetched object or None.

        :raises exceptions.TogglMultipleResultsException: When multiple results is returned base on the specified conditions.
        """
        if self.entity_cls is None:
            raise exceptions.TogglException(
                'The TogglSet instance is not binded to any TogglEntity!')

        config = config or utils.Config.factory()

        if id is not None:
            if self.can_get_detail:
                try:
                    fetched_entity = utils.toggl(self.build_detail_url(
                        id, config),
                                                 'get',
                                                 config=config)
                    if fetched_entity['data'] is None:
                        return None

                    return self.entity_cls.deserialize(
                        config=config, **fetched_entity['data'])
                except exceptions.TogglNotFoundException:
                    return None
            else:
                # TODO: [Q/Design] Is this desired fallback?
                # Most probably it is desired for Toggl usecase, because some Entities does not have detail view (eq. Users) and need
                # to do query for whole list and then filter out the entity based on ID.
                conditions['id'] = id

        entries = self.filter(config=config, **conditions)

        if len(entries) > 1:
            raise exceptions.TogglMultipleResultsException()

        if not entries:
            return None

        return entries[0]
Esempio n. 9
0
    def current(self, config=None):  # type: (utils.Config) -> TimeEntry
        """
        Method that returns currently running TimeEntry or None if there is no currently running time entry.

        :param config:
        :return:
        """
        config = config or utils.Config.factory()
        fetched_entity = utils.toggl('/time_entries/current', 'get', config=config)

        if fetched_entity.get('data') is None:
            return None

        return self.entity_cls.deserialize(config=config, **fetched_entity['data'])
Esempio n. 10
0
    def invite(self, *emails):  # type: (str) -> None
        """
        Invites users defined by email addresses. The users does not have to have account in Toggl, in that case after
        accepting the invitation, they will go through process of creating the account in the Toggl web.

        :param emails: List of emails to invite.
        :return: None
        """
        for email in emails:
            if not validate_email(email):
                raise exceptions.TogglValidationException('Supplied email \'{}\' is not valid email!'.format(email))

        emails_json = json.dumps({'emails': emails})
        data = utils.toggl("/workspaces/{}/invite".format(self.id), "post", emails_json, config=self._config)

        if 'notifications' in data and data['notifications']:
            raise exceptions.TogglException(data['notifications'])
Esempio n. 11
0
    def _fetch_all(self, url, order, config):  # type: (str, str, utils.Config) -> typing.List[Entity]
        """
        Helper method that fetches all objects from given URL and deserialize them.
        """
        fetched_entities = utils.toggl(url, 'get', config=config)

        if fetched_entities is None:
            return []

        output = []
        i = 0 if order == 'asc' else len(fetched_entities) - 1
        while 0 <= i < len(fetched_entities):
            output.append(self.entity_cls.deserialize(config=config, **fetched_entities[i]))

            if order == 'asc':
                i += 1
            else:
                i -= 1

        return output