Skip to content

Commit 01a74a1

Browse files
committed
[feat,style]: add webhook timeout handling and prettier
1 parent 929fb94 commit 01a74a1

2 files changed

Lines changed: 23 additions & 5 deletions

File tree

topgg/webhooks.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# SPDX-FileCopyrightText: 2024-2026 null8626 & Top.gg
44

55
from collections.abc import Awaitable, Callable
6+
from asyncio import wait_for, TimeoutError
67
from inspect import iscoroutinefunction
78
from aiohttp import test_utils, web
89
from typing import Any, TypeAlias
@@ -62,6 +63,8 @@ class Webhooks:
6263
:type port: :py:class:`int`
6364
:param app: The :class:`~aiohttp.web.Application` instance to use. Defaults to creating one with default configurations.
6465
:type app: :class:`~aiohttp.web.Application` | :class:`~aiohttp.test_utils.TestClient` | :py:obj:`None`
66+
:param timeout: The timeout for reading payloads in seconds. Defaults to one second.
67+
:type timeout: :py:class:`float`
6568
6669
:exception TypeError: One or more specified arguments has an invalid type.
6770
:exception ValueError: One or more specified arguments is empty.
@@ -77,6 +80,7 @@ class Webhooks:
7780
'__web_server',
7881
'__is_running',
7982
'__listeners',
83+
'__timeout',
8084
)
8185

8286
__route: str
@@ -96,6 +100,7 @@ def __init__(
96100
host: str = '0.0.0.0',
97101
port: int = 8080,
98102
app: web.Application | test_utils.TestClient | None = None,
103+
timeout: float = 1.0,
99104
):
100105
if (
101106
not isinstance(secret, str)
@@ -109,6 +114,8 @@ def __init__(
109114
raise ValueError('The specified secret, route, and/or host must not be empty.')
110115
elif port is not None and not isinstance(port, int):
111116
raise TypeError('The specified port must be an integer.')
117+
elif timeout is not None and not isinstance(timeout, float):
118+
raise TypeError('The specified timeout must be a float.')
112119

113120
self.__route = route
114121
self.__host = host
@@ -118,6 +125,7 @@ def __init__(
118125
self.__web_server = None
119126
self.__is_running = False
120127
self.__listeners = {}
128+
self.__timeout = timeout
121129

122130
def __repr__(self) -> str:
123131
return f'<{__class__.__name__} route={self.__route!r} host={self.__host!r} port={self.__port} is_running={self.is_running}>'
@@ -200,11 +208,13 @@ async def handler(request: web.Request) -> web.Response:
200208
try:
201209
assert request.body_exists and request.has_body and request.can_read_body
202210

203-
body = await request.text()
211+
body = await wait_for(request.text(), self.__timeout)
204212
json_body = json.loads(body)
205213

206214
payload_type = PayloadType(json_body['type'])
207215
payload = payload_type._deserialize(json_body['data'])
216+
except TimeoutError:
217+
return web.json_response({'error': 'Malformed request'}, status=400)
208218
except web.HTTPRequestEntityTooLarge: # pragma: nocover
209219
return web.json_response({'error': 'Request body too large'}, status=413)
210220
except Exception as err: # pragma: nocover

topgg/widget.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ def large(platform: Platform, project_type: ProjectType, id: int) -> str:
2929
"""
3030

3131
if not isinstance(project_type, ProjectType) or not isinstance(id, int):
32-
raise TypeError("The specified platform, project type, and/or project ID's type is invalid.")
32+
raise TypeError(
33+
"The specified platform, project type, and/or project ID's type is invalid."
34+
)
3335

3436
return f'{BASE_URL}/widgets/large/{platform.value}/{project_type.value}/{id}'
3537

@@ -52,7 +54,9 @@ def votes(platform: Platform, project_type: ProjectType, id: int) -> str:
5254
"""
5355

5456
if not isinstance(project_type, ProjectType) or not isinstance(id, int):
55-
raise TypeError("The specified platform, project type, and/or project ID's type is invalid.")
57+
raise TypeError(
58+
"The specified platform, project type, and/or project ID's type is invalid."
59+
)
5660

5761
return f'{BASE_URL}/widgets/small/votes/{platform.value}/{project_type.value}/{id}'
5862

@@ -75,7 +79,9 @@ def owner(platform: Platform, project_type: ProjectType, id: int) -> str:
7579
"""
7680

7781
if not isinstance(project_type, ProjectType) or not isinstance(id, int):
78-
raise TypeError("The specified platform, project type, and/or project ID's type is invalid.")
82+
raise TypeError(
83+
"The specified platform, project type, and/or project ID's type is invalid."
84+
)
7985

8086
return f'{BASE_URL}/widgets/small/owner/{platform.value}/{project_type.value}/{id}'
8187

@@ -98,6 +104,8 @@ def social(platform: Platform, project_type: ProjectType, id: int) -> str:
98104
"""
99105

100106
if not isinstance(project_type, ProjectType) or not isinstance(id, int):
101-
raise TypeError("The specified platform, project type, and/or project ID's type is invalid.")
107+
raise TypeError(
108+
"The specified platform, project type, and/or project ID's type is invalid."
109+
)
102110

103111
return f'{BASE_URL}/widgets/small/social/{platform.value}/{project_type.value}/{id}'

0 commit comments

Comments
 (0)