Skip to content

Tags

toggl_api.TagEndpoint

Bases: TogglCachedEndpoint[TogglTag]

Specific endpoints for retrieving and modifying tags.

Official Documentation

Examples:

>>> tag_endpoint = TagEndpoint(21341214, BasicAuth(...), JSONCache(Path("cache")))
>>> tag_endpoint.add("Eucalyptus")
TogglTag(213123132, "Eucalyptus")
>>> tag_endpoint.query(TogglQuery("name", "Eucalyptus"))
[TogglTag(213123132, "Eucalyptus")]

Parameters:

  • workspace_id (int | TogglWorkspace) –

    The workspace the tags belong to.

  • auth (BasicAuth) –

    Authentication for the client.

  • cache (TogglCache[TogglTag]) –

    Cache object where tags are stored.

  • timeout (int, default: 10 ) –

    How long it takes for the client to timeout. Keyword Only. Defaults to 10 seconds.

  • re_raise (bool, default: False ) –

    Whether to raise all HTTPStatusError errors and not handle them internally. Keyword Only.

  • retries (int, default: 3 ) –

    Max retries to attempt if the server returns a 5xx status_code. Has no effect if re_raise is True. Keyword Only

Source code in toggl_api/tag.py
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
class TagEndpoint(TogglCachedEndpoint[TogglTag]):
    """Specific endpoints for retrieving and modifying tags.

    [Official Documentation](https://engineering.toggl.com/docs/api/tags)

    Examples:
        >>> tag_endpoint = TagEndpoint(21341214, BasicAuth(...), JSONCache(Path("cache")))
        >>> tag_endpoint.add("Eucalyptus")
        TogglTag(213123132, "Eucalyptus")

        >>> tag_endpoint.query(TogglQuery("name", "Eucalyptus"))
        [TogglTag(213123132, "Eucalyptus")]

    Params:
        workspace_id: The workspace the tags belong to.
        auth: Authentication for the client.
        cache: Cache object where tags are stored.
        timeout: How long it takes for the client to timeout. Keyword Only.
            Defaults to 10 seconds.
        re_raise: Whether to raise all HTTPStatusError errors and not handle them
            internally. Keyword Only.
        retries: Max retries to attempt if the server returns a *5xx* status_code.
            Has no effect if re_raise is `True`. Keyword Only
    """

    def __init__(
        self,
        workspace_id: int | TogglWorkspace,
        auth: BasicAuth,
        cache: TogglCache[TogglTag],
        *,
        timeout: int = 10,
        re_raise: bool = False,
        retries: int = 3,
    ) -> None:
        super().__init__(
            0,
            auth,
            cache,
            timeout=timeout,
            re_raise=re_raise,
            retries=retries,
        )
        self.workspace_id = workspace_id if isinstance(workspace_id, int) else workspace_id.id

    def get(self, tag: TogglTag | int, *, refresh: bool = False) -> TogglTag | None:
        """Get endpoint convenience method for querying single tags from cache.

        This endpoint doesn't exist on the API so it locally queries for tags
        instead.

        Examples:
            >>> toggl_endpoint.get(213123132)
            TogglTag(213123132, "Eucalyptus")

        Args:
            tag: Which tag to retrieve. Can be an existing model or its id.
            refresh: Whether to collect all tags from the API first.

        Returns:
            A tag model if it was found otherwise None.
        """
        if self.cache is None:
            return None

        if refresh:
            try:
                self.collect(refresh=True)
            except HTTPStatusError:
                if self.re_raise:
                    raise
                log.exception("%s")

        if isinstance(tag, TogglTag):
            tag = tag.id

        query = self.query(TogglQuery("id", tag))
        if query:
            return query[0]

        return None

    def collect(self, *, refresh: bool = False) -> list[TogglTag]:
        """Gather all tags.

        [Official Documentation](https://engineering.toggl.com/docs/api/tags#get-tags)

        Raises:
            HTTPStatusError: If any issue happens with the Toggl API.

        Returns:
            A list of tags collected from the API or local cache.
        """
        return self.request("", refresh=refresh)

    def add(self, name: str) -> TogglTag:
        """Create a new tag.

        This endpoint always hit the external API in order to keep tags consistent.

        [Official Documentation](https://engineering.toggl.com/docs/api/tags#post-create-tag)

        Args:
            name: The name of the new tag.

        Raises:
            NamingError: IF the tag name is empty.
            HTTPStatusError: If a tag with the same name exists or any other
                none *ok* status code is returned.

        Returns:
            The newly created tag.
        """

        if not name:
            msg = "The tag name needs to be at least one character long."
            raise NamingError(msg)

        return self.request(
            "",
            body={"name": name},
            method=RequestMethod.POST,
            refresh=True,
        )

    def edit(self, tag: TogglTag | int, name: Optional[str] = None) -> TogglTag:
        """Sets the name of the tag based on the tag object.

        This endpoint always hit the external API in order to keep tags consistent.

        [Official Documentation](https://engineering.toggl.com/docs/api/tags#put-update-tag)

        Examples:
            >>> tag = Tag(213123132, "Eucalyptus")
            >>> tag_endpoint.edit(tag)
            TogglTag(213123132, "Eucalyptus")

            >>> tag_endpoint.edit(213123132, "Eucalyptus")
            TogglTag(213123132, "Eucalyptus")

        Args:
            tag: TogglTag or integer as the id.
                *Currently can accept the tag as the 'name' input as well.*
            name: New name for the tag. Will become required in the next major
                version.

        Raises:
            NamingError: If the name is not at the minimum length.
            HTTPStatusError: If any issue happens with the Toggl API.

        Returns:
            The edited tag.
        """

        if isinstance(tag, TogglTag) and name is None:
            warnings.warn(
                "DEPRECATED: the 'name' argument will replace the internal usage of the 'Tag.name' attribute.",
                stacklevel=2,
            )
            name = tag.name

        if not name:
            msg = "The tag name needs to be at least one character long."
            raise NamingError(msg)

        return self.request(
            f"/{tag.id if isinstance(tag, TogglTag) else tag}",
            body={"name": name},
            method=RequestMethod.PUT,
            refresh=True,
        )

    def delete(self, tag: TogglTag | int) -> None:
        """Deletes a tag based on its ID or model.

        This endpoint always hit the external API in order to keep tags consistent.

        [Official Documentation](https://engineering.toggl.com/docs/api/tags#delete-delete-tag)

        Args:
            tag: The tag to delete. Either the id or model.

        Raises:
            HTTPStatusError: For anything thats not an '2xx' or '404' code.
        """
        tag_id = tag if isinstance(tag, int) else tag.id
        try:
            self.request(
                f"/{tag_id}",
                method=RequestMethod.DELETE,
                refresh=True,
            )
        except HTTPStatusError as err:
            if self.re_raise or err.response.status_code != codes.NOT_FOUND:
                raise
            log.warning(
                "Tag with id %s was either already deleted or did not exist in the first place!",
                tag_id,
            )

        if isinstance(tag, int):
            tag_model = self.cache.find_entry({"id": tag})
            if not isinstance(tag_model, TogglTag):
                return
            tag = tag_model

        self.cache.delete_entries(tag)
        self.cache.commit()

    @property
    def endpoint(self) -> str:
        return f"workspaces/{self.workspace_id}/tags"

    @property
    def model(self) -> type[TogglTag]:
        return TogglTag

collect(*, refresh: bool = False) -> list[TogglTag]

Gather all tags.

Official Documentation

Raises:

  • HTTPStatusError

    If any issue happens with the Toggl API.

Returns:

  • list[TogglTag]

    A list of tags collected from the API or local cache.

Source code in toggl_api/tag.py
107
108
109
110
111
112
113
114
115
116
117
118
def collect(self, *, refresh: bool = False) -> list[TogglTag]:
    """Gather all tags.

    [Official Documentation](https://engineering.toggl.com/docs/api/tags#get-tags)

    Raises:
        HTTPStatusError: If any issue happens with the Toggl API.

    Returns:
        A list of tags collected from the API or local cache.
    """
    return self.request("", refresh=refresh)

add(name: str) -> TogglTag

Create a new tag.

This endpoint always hit the external API in order to keep tags consistent.

Official Documentation

Parameters:

  • name (str) –

    The name of the new tag.

Raises:

  • NamingError

    IF the tag name is empty.

  • HTTPStatusError

    If a tag with the same name exists or any other none ok status code is returned.

Returns:

Source code in toggl_api/tag.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
def add(self, name: str) -> TogglTag:
    """Create a new tag.

    This endpoint always hit the external API in order to keep tags consistent.

    [Official Documentation](https://engineering.toggl.com/docs/api/tags#post-create-tag)

    Args:
        name: The name of the new tag.

    Raises:
        NamingError: IF the tag name is empty.
        HTTPStatusError: If a tag with the same name exists or any other
            none *ok* status code is returned.

    Returns:
        The newly created tag.
    """

    if not name:
        msg = "The tag name needs to be at least one character long."
        raise NamingError(msg)

    return self.request(
        "",
        body={"name": name},
        method=RequestMethod.POST,
        refresh=True,
    )

edit(tag: TogglTag | int, name: Optional[str] = None) -> TogglTag

Sets the name of the tag based on the tag object.

This endpoint always hit the external API in order to keep tags consistent.

Official Documentation

Examples:

>>> tag = Tag(213123132, "Eucalyptus")
>>> tag_endpoint.edit(tag)
TogglTag(213123132, "Eucalyptus")
>>> tag_endpoint.edit(213123132, "Eucalyptus")
TogglTag(213123132, "Eucalyptus")

Parameters:

  • tag (TogglTag | int) –

    TogglTag or integer as the id. Currently can accept the tag as the 'name' input as well.

  • name (Optional[str], default: None ) –

    New name for the tag. Will become required in the next major version.

Raises:

  • NamingError

    If the name is not at the minimum length.

  • HTTPStatusError

    If any issue happens with the Toggl API.

Returns:

Source code in toggl_api/tag.py
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def edit(self, tag: TogglTag | int, name: Optional[str] = None) -> TogglTag:
    """Sets the name of the tag based on the tag object.

    This endpoint always hit the external API in order to keep tags consistent.

    [Official Documentation](https://engineering.toggl.com/docs/api/tags#put-update-tag)

    Examples:
        >>> tag = Tag(213123132, "Eucalyptus")
        >>> tag_endpoint.edit(tag)
        TogglTag(213123132, "Eucalyptus")

        >>> tag_endpoint.edit(213123132, "Eucalyptus")
        TogglTag(213123132, "Eucalyptus")

    Args:
        tag: TogglTag or integer as the id.
            *Currently can accept the tag as the 'name' input as well.*
        name: New name for the tag. Will become required in the next major
            version.

    Raises:
        NamingError: If the name is not at the minimum length.
        HTTPStatusError: If any issue happens with the Toggl API.

    Returns:
        The edited tag.
    """

    if isinstance(tag, TogglTag) and name is None:
        warnings.warn(
            "DEPRECATED: the 'name' argument will replace the internal usage of the 'Tag.name' attribute.",
            stacklevel=2,
        )
        name = tag.name

    if not name:
        msg = "The tag name needs to be at least one character long."
        raise NamingError(msg)

    return self.request(
        f"/{tag.id if isinstance(tag, TogglTag) else tag}",
        body={"name": name},
        method=RequestMethod.PUT,
        refresh=True,
    )

delete(tag: TogglTag | int) -> None

Deletes a tag based on its ID or model.

This endpoint always hit the external API in order to keep tags consistent.

Official Documentation

Parameters:

  • tag (TogglTag | int) –

    The tag to delete. Either the id or model.

Raises:

  • HTTPStatusError

    For anything thats not an '2xx' or '404' code.

Source code in toggl_api/tag.py
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
def delete(self, tag: TogglTag | int) -> None:
    """Deletes a tag based on its ID or model.

    This endpoint always hit the external API in order to keep tags consistent.

    [Official Documentation](https://engineering.toggl.com/docs/api/tags#delete-delete-tag)

    Args:
        tag: The tag to delete. Either the id or model.

    Raises:
        HTTPStatusError: For anything thats not an '2xx' or '404' code.
    """
    tag_id = tag if isinstance(tag, int) else tag.id
    try:
        self.request(
            f"/{tag_id}",
            method=RequestMethod.DELETE,
            refresh=True,
        )
    except HTTPStatusError as err:
        if self.re_raise or err.response.status_code != codes.NOT_FOUND:
            raise
        log.warning(
            "Tag with id %s was either already deleted or did not exist in the first place!",
            tag_id,
        )

    if isinstance(tag, int):
        tag_model = self.cache.find_entry({"id": tag})
        if not isinstance(tag_model, TogglTag):
            return
        tag = tag_model

    self.cache.delete_entries(tag)
    self.cache.commit()