Skip to content

Client

toggl_api.ClientBody dataclass

Bases: BaseBody

JSON body dataclass for PUT, POST & PATCH requests.

METHOD DESCRIPTION
format

Format the body for JSON requests.

ATTRIBUTE DESCRIPTION
name

Name of the client. Defaults to None. Will be required if its a POST request.

TYPE: str | None

status

Status of the client. API defaults to active. Premium Feature.

TYPE: CLIENT_STATUS | None

name class-attribute instance-attribute

name: str | None = field(default=None)

Name of the client. Defaults to None. Will be required if its a POST request.

status class-attribute instance-attribute

status: CLIENT_STATUS | None = field(default=None)

Status of the client. API defaults to active. Premium Feature.

format

format(endpoint: str, **body: Any) -> dict[str, Any]

Format the body for JSON requests.

Gets called by the endpoint methods before requesting.

PARAMETER DESCRIPTION
endpoint

API endpoint for filtering purposes.

TYPE: str

body

Any additonal body content that the endpoint request requires. If passing workspace id to client endpoints use 'wid' instead.

TYPE: Any DEFAULT: {}

RETURNS DESCRIPTION
dict[str, Any]

JSON compatible formatted body.

toggl_api.ClientEndpoint

Bases: TogglCachedEndpoint[TogglClient]

Specific endpoints for retrieving and modifying clients.

Official Documentation

Examples:

>>> wid = 123213324
>>> client_endpoint = ClientEndpoint(wid, BasicAuth(...), SqliteCache(...))
>>> client_endpoint.get(214125562)
TogglClient(214125562, "Simplicidentata", workspace=123213324)
PARAMETER DESCRIPTION
workspace_id

The workspace the clients belong to.

TYPE: int | TogglWorkspace

auth

Authentication for the client.

TYPE: BasicAuth

cache

Cache object where the clients will stored and handled.

TYPE: TogglCache[TogglClient] | None DEFAULT: None

client

Optional client to be passed to be used for requests. Useful when a global client is used and needs to be recycled.

TYPE: Client | None DEFAULT: None

timeout

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

TYPE: Timeout | int DEFAULT: 10

re_raise

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

TYPE: bool DEFAULT: False

retries

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

TYPE: int DEFAULT: 3

METHOD DESCRIPTION
add

Create a Client based on parameters set in the provided body.

get

Request a client based on its id.

edit

Edit a client with the supplied parameters from the body.

delete

Delete a client based on its ID.

collect

Request all Clients based on status and name if specified in the body.

Source code in src/toggl_api/_client.py
 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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
class ClientEndpoint(TogglCachedEndpoint[TogglClient]):
    """Specific endpoints for retrieving and modifying clients.

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

    Examples:
        >>> wid = 123213324
        >>> client_endpoint = ClientEndpoint(wid, BasicAuth(...), SqliteCache(...))
        >>> client_endpoint.get(214125562)
        TogglClient(214125562, "Simplicidentata", workspace=123213324)

    Params:
        workspace_id: The workspace the clients belong to.
        auth: Authentication for the client.
        cache: Cache object where the clients will stored and handled.
        client: Optional client to be passed to be used for requests. Useful
            when a global client is used and needs to be recycled.
        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.
    """

    MODEL = TogglClient

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

    def add(self, body: ClientBody) -> TogglClient:
        """Create a Client based on parameters set in the provided body.

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

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

        Args:
            body: New parameters for the client to be created.

        Raises:
            NamingError: If no name was set as its required.

        Returns:
            Newly created client with specified parameters.
        """
        if body.name is None:
            msg = "Name must be set in order to create a client!"
            raise NamingError(msg)

        return cast(
            "TogglClient",
            self.request(
                self.endpoint,
                body=body.format("add", wid=self.workspace_id),
                method=RequestMethod.POST,
                refresh=True,
            ),
        )

    def get(self, client_id: int | TogglClient, *, refresh: bool = False) -> TogglClient | None:
        """Request a client based on its id.

        [Official Documentation](https://engineering.toggl.com/docs/api/clients#get-load-client-from-id)

        Args:
            client_id: Which client to look for.
            refresh: Whether to only check cache. It will default to True if id
                is not found in cache. Defaults to False.

        Raises:
            HTTPStatusError: Any error that is not a 404 code or `re_raise` is True.

        Returns:
            A TogglClient if the client was found else None.
        """
        if isinstance(client_id, TogglClient):
            client_id = client_id.id

        if self.cache and not refresh:
            return self.cache.find({"id": client_id})

        try:
            response = self.request(
                f"{self.endpoint}/{client_id}",
                refresh=refresh,
            )
        except HTTPStatusError as err:
            if not self.re_raise and err.response.status_code == codes.NOT_FOUND:
                log.warning("Client with id %s does not exist!", client_id)
                return None
            raise

        return cast("TogglClient", response) or None

    def edit(self, client: TogglClient | int, body: ClientBody) -> TogglClient:
        """Edit a client with the supplied parameters from the body.

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

        [Official Documentation](https://engineering.toggl.com/docs/api/clients#put-change-client)

        Args:
            client: Target client to edit.
            body: New parameters to use. Ignore client status.

        Returns:
            Newly edited client or None if not found.
        """
        if body.status:
            log.warning("Client status not supported by edit endpoint")
            body.status = None

        if isinstance(client, TogglClient):
            client = client.id

        return cast(
            "TogglClient",
            self.request(
                f"{self.endpoint}/{client}",
                body=body.format("edit", wid=self.workspace_id),
                method=RequestMethod.PUT,
                refresh=True,
            ),
        )

    def delete(self, client: TogglClient | int) -> None:
        """Delete a client based on its ID.

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

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

        Raises:
            HTTPStatusError: If anything thats not an *ok* or *404* status code
                is returned.
        """
        client_id = client if isinstance(client, int) else client.id
        try:
            self.request(f"{self.endpoint}/{client_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(
                "Client with id %s was either already deleted or did not exist in the first place!",
                client_id,
            )
        if self.cache is None:
            return
        if isinstance(client, int):
            clt = self.cache.find({"id": client})
            if not isinstance(clt, TogglClient):
                return
            client = clt

        self.cache.delete(client)
        self.cache.commit()

    def _collect_cache(self, body: ClientBody | None) -> list[TogglClient]:
        if body and body.status is not None:
            log.warning("Client body 'status' parameter is not implemented with cache!")

        if body and body.name:
            return list(self.query(TogglQuery("name", body.name)))

        return list(self.load_cache())

    def collect(
        self,
        body: ClientBody | None = None,
        *,
        refresh: bool = False,
    ) -> list[TogglClient]:
        """Request all Clients based on status and name if specified in the body.

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

        Args:
            body: Status and name to target. Ignores notes. Ignores status if using cache.
            refresh: Whether to refresh cache.

        Returns:
            A list of clients. Empty if not found.
        """
        if self.cache and not refresh:
            return self._collect_cache(body)

        url = self.endpoint
        if body and body.status:
            url += f"?{body.status}"
        if body and body.name:
            if body.status:
                url += "&"
            else:
                url += "?"
            url += f"{body.name}"

        response = self.request(url, method=RequestMethod.GET, refresh=refresh)
        return response if isinstance(response, list) else []

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

add

add(body: ClientBody) -> TogglClient

Create a Client based on parameters set in the provided body.

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

Official Documentation

PARAMETER DESCRIPTION
body

New parameters for the client to be created.

TYPE: ClientBody

RAISES DESCRIPTION
NamingError

If no name was set as its required.

RETURNS DESCRIPTION
TogglClient

Newly created client with specified parameters.

Source code in src/toggl_api/_client.py
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
def add(self, body: ClientBody) -> TogglClient:
    """Create a Client based on parameters set in the provided body.

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

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

    Args:
        body: New parameters for the client to be created.

    Raises:
        NamingError: If no name was set as its required.

    Returns:
        Newly created client with specified parameters.
    """
    if body.name is None:
        msg = "Name must be set in order to create a client!"
        raise NamingError(msg)

    return cast(
        "TogglClient",
        self.request(
            self.endpoint,
            body=body.format("add", wid=self.workspace_id),
            method=RequestMethod.POST,
            refresh=True,
        ),
    )

get

get(
    client_id: int | TogglClient, *, refresh: bool = False
) -> TogglClient | None

Request a client based on its id.

Official Documentation

PARAMETER DESCRIPTION
client_id

Which client to look for.

TYPE: int | TogglClient

refresh

Whether to only check cache. It will default to True if id is not found in cache. Defaults to False.

TYPE: bool DEFAULT: False

RAISES DESCRIPTION
HTTPStatusError

Any error that is not a 404 code or re_raise is True.

RETURNS DESCRIPTION
TogglClient | None

A TogglClient if the client was found else None.

Source code in src/toggl_api/_client.py
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
def get(self, client_id: int | TogglClient, *, refresh: bool = False) -> TogglClient | None:
    """Request a client based on its id.

    [Official Documentation](https://engineering.toggl.com/docs/api/clients#get-load-client-from-id)

    Args:
        client_id: Which client to look for.
        refresh: Whether to only check cache. It will default to True if id
            is not found in cache. Defaults to False.

    Raises:
        HTTPStatusError: Any error that is not a 404 code or `re_raise` is True.

    Returns:
        A TogglClient if the client was found else None.
    """
    if isinstance(client_id, TogglClient):
        client_id = client_id.id

    if self.cache and not refresh:
        return self.cache.find({"id": client_id})

    try:
        response = self.request(
            f"{self.endpoint}/{client_id}",
            refresh=refresh,
        )
    except HTTPStatusError as err:
        if not self.re_raise and err.response.status_code == codes.NOT_FOUND:
            log.warning("Client with id %s does not exist!", client_id)
            return None
        raise

    return cast("TogglClient", response) or None

edit

edit(client: TogglClient | int, body: ClientBody) -> TogglClient

Edit a client with the supplied parameters from the body.

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

Official Documentation

PARAMETER DESCRIPTION
client

Target client to edit.

TYPE: TogglClient | int

body

New parameters to use. Ignore client status.

TYPE: ClientBody

RETURNS DESCRIPTION
TogglClient

Newly edited client or None if not found.

Source code in src/toggl_api/_client.py
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
def edit(self, client: TogglClient | int, body: ClientBody) -> TogglClient:
    """Edit a client with the supplied parameters from the body.

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

    [Official Documentation](https://engineering.toggl.com/docs/api/clients#put-change-client)

    Args:
        client: Target client to edit.
        body: New parameters to use. Ignore client status.

    Returns:
        Newly edited client or None if not found.
    """
    if body.status:
        log.warning("Client status not supported by edit endpoint")
        body.status = None

    if isinstance(client, TogglClient):
        client = client.id

    return cast(
        "TogglClient",
        self.request(
            f"{self.endpoint}/{client}",
            body=body.format("edit", wid=self.workspace_id),
            method=RequestMethod.PUT,
            refresh=True,
        ),
    )

delete

delete(client: TogglClient | int) -> None

Delete a client based on its ID.

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

Official Documentation

RAISES DESCRIPTION
HTTPStatusError

If anything thats not an ok or 404 status code is returned.

Source code in src/toggl_api/_client.py
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
def delete(self, client: TogglClient | int) -> None:
    """Delete a client based on its ID.

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

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

    Raises:
        HTTPStatusError: If anything thats not an *ok* or *404* status code
            is returned.
    """
    client_id = client if isinstance(client, int) else client.id
    try:
        self.request(f"{self.endpoint}/{client_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(
            "Client with id %s was either already deleted or did not exist in the first place!",
            client_id,
        )
    if self.cache is None:
        return
    if isinstance(client, int):
        clt = self.cache.find({"id": client})
        if not isinstance(clt, TogglClient):
            return
        client = clt

    self.cache.delete(client)
    self.cache.commit()

collect

collect(
    body: ClientBody | None = None, *, refresh: bool = False
) -> list[TogglClient]

Request all Clients based on status and name if specified in the body.

Official Documentation

PARAMETER DESCRIPTION
body

Status and name to target. Ignores notes. Ignores status if using cache.

TYPE: ClientBody | None DEFAULT: None

refresh

Whether to refresh cache.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
list[TogglClient]

A list of clients. Empty if not found.

Source code in src/toggl_api/_client.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
def collect(
    self,
    body: ClientBody | None = None,
    *,
    refresh: bool = False,
) -> list[TogglClient]:
    """Request all Clients based on status and name if specified in the body.

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

    Args:
        body: Status and name to target. Ignores notes. Ignores status if using cache.
        refresh: Whether to refresh cache.

    Returns:
        A list of clients. Empty if not found.
    """
    if self.cache and not refresh:
        return self._collect_cache(body)

    url = self.endpoint
    if body and body.status:
        url += f"?{body.status}"
    if body and body.name:
        if body.status:
            url += "&"
        else:
            url += "?"
        url += f"{body.name}"

    response = self.request(url, method=RequestMethod.GET, refresh=refresh)
    return response if isinstance(response, list) else []

toggl_api.asyncio.AsyncClientEndpoint

Bases: TogglAsyncCachedEndpoint[TogglClient]

Specific endpoints for retrieving and modifying clients.

Official Documentation

Examples:

>>> wid = 123213324
>>> client_endpoint = AsyncClientEndpoint(wid, BasicAuth(...), AsyncSqliteCache(...))
>>> await client_endpoint.get(214125562)
TogglClient(214125562, "Simplicidentata", workspace=123213324)
PARAMETER DESCRIPTION
workspace_id

The workspace the clients belong to.

TYPE: int | TogglWorkspace

auth

Authentication for the client.

TYPE: BasicAuth

cache

Cache object where the clients will stored and handled.

TYPE: AsyncSqliteCache[TogglClient] | None DEFAULT: None

client

Optional async client to be passed to be used for requests.

TYPE: AsyncClient | None DEFAULT: None

timeout

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

TYPE: int DEFAULT: 10

re_raise

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

TYPE: bool DEFAULT: False

retries

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

TYPE: int DEFAULT: 3

METHOD DESCRIPTION
add

Create a Client based on parameters set in the provided body.

get

Request a client based on its id.

edit

Edit a client with the supplied parameters from the body.

delete

Delete a client based on its ID.

collect

Request all Clients based on status and name if specified in the body.

add async

add(body: ClientBody) -> TogglClient

Create a Client based on parameters set in the provided body.

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

Official Documentation

PARAMETER DESCRIPTION
body

New parameters for the client to be created.

TYPE: ClientBody

RAISES DESCRIPTION
NamingError

If no name was set as its required.

RETURNS DESCRIPTION
TogglClient

Newly created client with specified parameters.

get async

get(
    client_id: int | TogglClient, *, refresh: bool = False
) -> TogglClient | None

Request a client based on its id.

Official Documentation

PARAMETER DESCRIPTION
client_id

Which client to look for.

TYPE: int | TogglClient

refresh

Whether to only check cache. It will default to True if id is not found in cache. Defaults to False.

TYPE: bool DEFAULT: False

RAISES DESCRIPTION
HTTPStatusError

If anything thats not 'ok' or a 404 is returned.

RETURNS DESCRIPTION
TogglClient | None

A TogglClient if the client was found else None.

edit async

edit(client: TogglClient | int, body: ClientBody) -> TogglClient

Edit a client with the supplied parameters from the body.

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

Official Documentation

PARAMETER DESCRIPTION
client

Target client to edit.

TYPE: TogglClient | int

body

New parameters to use. Ignore client status.

TYPE: ClientBody

RETURNS DESCRIPTION
TogglClient

Newly edited client or None if not found.

delete async

delete(client: TogglClient | int) -> None

Delete a client based on its ID.

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

Official Documentation

RAISES DESCRIPTION
HTTPStatusError

If anything thats not an ok or 404 status code is returned.

collect async

collect(
    body: ClientBody | None = None, *, refresh: bool = False
) -> list[TogglClient]

Request all Clients based on status and name if specified in the body.

Official Documentation

PARAMETER DESCRIPTION
body

Status and name to target. Ignores notes. Ignores status if using cache.

TYPE: ClientBody | None DEFAULT: None

refresh

Whether to refresh cache.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
list[TogglClient]

A list of clients. Empty if not found.