Skip to content

Commit 801e1e5

Browse files
committed
Add paste service utility
1 parent 1a362c0 commit 801e1e5

File tree

4 files changed

+101
-2
lines changed

4 files changed

+101
-2
lines changed

docs/changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
44
Changelog
55
=========
6+
- :release:`9.7.0 <4th June 2023>`
7+
- :feature:`179` Add paste service utility to upload text to our paste service.
68
- :feature:`176` Migrate repo to use ruff for linting
79

810

pydis_core/utils/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"""Useful utilities and tools for Discord bot development."""
22

33
from pydis_core.utils import (
4-
_monkey_patches, caching, channel, commands, cooldown, function, interactions, logging, members, regex, scheduling
4+
_monkey_patches, caching, channel, commands, cooldown, function, interactions, logging, members, paste_service,
5+
regex, scheduling
56
)
67
from pydis_core.utils._extensions import unqualify
78

@@ -32,6 +33,7 @@ def apply_monkey_patches() -> None:
3233
interactions,
3334
logging,
3435
members,
36+
paste_service,
3537
regex,
3638
scheduling,
3739
unqualify,

pydis_core/utils/paste_service.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
from aiohttp import ClientConnectorError, ClientSession
2+
3+
from pydis_core.utils import logging
4+
5+
log = logging.get_logger(__name__)
6+
7+
FAILED_REQUEST_ATTEMPTS = 3
8+
MAX_PASTE_SIZE = 128 * 1024 # 128kB
9+
10+
11+
class PasteUploadError(Exception):
12+
"""Raised when an error is encountered uploading to the paste service."""
13+
14+
15+
class PasteTooLongError(Exception):
16+
"""Raised when content is too large to upload to the paste service."""
17+
18+
19+
async def send_to_paste_service(
20+
*,
21+
contents: str,
22+
paste_url: str,
23+
http_session: ClientSession,
24+
file_name: str ="",
25+
lexer: str = "python",
26+
max_size: int = MAX_PASTE_SIZE,
27+
) -> str:
28+
"""
29+
Upload some contents to the paste service.
30+
31+
Args:
32+
contents: The content to upload to the paste service.
33+
paste_url: The base url to the paste service.
34+
http_session (aiohttp.ClientSession): The session to use when POSTing the content to the paste service.
35+
file_name: The name of the file to save to the paste service.
36+
lexer: The lexer to save the content with.
37+
max_size: The maximum number of bytes to be allowed. Anything larger that 256kB will be rejected.
38+
39+
Raises:
40+
:exc:`ValueError`: ``max_length`` greater than the maximum allowed by the paste service.
41+
:exc:`PasteTooLongError`: ``contents`` too long to upload.
42+
:exc:`PasteUploadError`: Uploading failed.
43+
44+
Returns:
45+
The URL to the uploaded contents.
46+
"""
47+
if max_size > MAX_PASTE_SIZE:
48+
raise ValueError(f"`max_length` must not be greater than {MAX_PASTE_SIZE}")
49+
50+
contents_size = len(contents.encode())
51+
if contents_size > max_size:
52+
log.info("Contents too large to send to paste service.")
53+
raise PasteTooLongError(f"Contents of size {contents_size} greater than maximum size {max_size}")
54+
55+
log.debug(f"Sending contents of size {contents_size} bytes to paste service.")
56+
payload = {
57+
"expiry": "1month",
58+
"files": [
59+
{"name": file_name, "lexer": lexer, "content": contents},
60+
]
61+
}
62+
for attempt in range(1, FAILED_REQUEST_ATTEMPTS + 1):
63+
try:
64+
async with http_session.post(f"{paste_url}/api/v1/paste", json=payload) as response:
65+
response_json = await response.json()
66+
except ClientConnectorError:
67+
log.warning(
68+
f"Failed to connect to paste service at url {paste_url}, "
69+
f"trying again ({attempt}/{FAILED_REQUEST_ATTEMPTS})."
70+
)
71+
continue
72+
except Exception:
73+
log.exception(
74+
f"An unexpected error has occurred during handling of the request, "
75+
f"trying again ({attempt}/{FAILED_REQUEST_ATTEMPTS})."
76+
)
77+
continue
78+
79+
if response.status == 400:
80+
log.warning(
81+
f"Paste service returned error {response_json['message']} with status code {response.status}, "
82+
f"trying again ({attempt}/{FAILED_REQUEST_ATTEMPTS})."
83+
)
84+
continue
85+
86+
if link := response_json.get("link" ):
87+
log.info(f"Successfully uploaded contents to {link}.")
88+
return link
89+
90+
log.warning(
91+
f"Got unexpected JSON response from paste service: {response_json}\n"
92+
f"trying again ({attempt}/{FAILED_REQUEST_ATTEMPTS})."
93+
)
94+
95+
raise PasteUploadError("Failed to upload contents to paste service")

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "pydis_core"
3-
version = "9.6.0"
3+
version = "9.7.0"
44
description = "PyDis core provides core functionality and utility to the bots of the Python Discord community."
55
authors = ["Python Discord <info@pythondiscord.com>"]
66
license = "MIT"

0 commit comments

Comments
 (0)