Skip to content

Commit d999fa1

Browse files
authoredJun 30, 2023
Error handling (#18)
## Popis: Spravení error handlingu z ```py if error_handling(answers): return await message.edit(embed=PollEmbedBase(error_handling(answers))) ``` na ```py if len(answer) > Poll.MAX_OPTIONS: raise TooManyOptionsError(f"Zadal jsi příliš mnoho odpovědí, můžeš maximálně {Poll.MAX_OPTIONS}!") ``` ## Proč? Zvýšení čtitelnosti, jednodušší práce s error handlingem a přehlednost. ## Co dodělat? - [x] #16
2 parents 8a2cf62 + 965bffa commit d999fa1

12 files changed

+177
-134
lines changed
 
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
on: push
2+
3+
jobs:
4+
build:
5+
runs-on: ubuntu-latest
6+
7+
steps:
8+
- uses: actions/checkout@v1
9+
10+
- name: Set up CPython
11+
uses: actions/setup-python@v4
12+
13+
- name: Install dependencies
14+
id: install-deps
15+
run: |
16+
python -m pip install --upgrade pip setuptools wheel ruff
17+
18+
- name: Black format
19+
uses: psf/black@stable
20+
21+
- name: Ruff Check
22+
run: ruff check .
23+
24+
25+

‎cogs/error.py

+41-31
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,58 @@
1-
import logging
2-
1+
from discord import Interaction
2+
from discord.app_commands import CommandInvokeError
33
from discord.ext import commands
4+
from loguru import logger
5+
6+
from src.ui.embeds import ErrorMessage
47

58

69
class Error(commands.Cog):
710
"""Basic class for catching errors and sending a message"""
811

912
def __init__(self, bot):
1013
self.bot = bot
11-
12-
self.logger = logging.getLogger("discord")
13-
handler = logging.FileHandler(
14-
filename="discord.log",
15-
encoding="utf-8",
16-
mode="w",
17-
)
18-
handler.setFormatter(
19-
logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s"),
20-
)
21-
self.logger.addHandler(handler)
14+
tree = self.bot.tree
15+
self._old_tree_error = tree.on_error
16+
tree.on_error = self.on_app_command_error
2217

2318
@commands.Cog.listener()
24-
async def on_command_error(
25-
self,
26-
ctx: commands.Context,
27-
error: commands.CommandError,
28-
):
19+
async def on_app_command_error(self, interaction: Interaction, error: Exception):
2920
match error:
30-
case commands.MissingPermissions():
31-
return await ctx.send("Chybí ti požadovaná práva!")
32-
33-
case commands.CommandNotFound():
34-
return None
35-
21+
case PrettyError():
22+
# if I use only 'error', gives me NoneType. Solved by this
23+
logger.error(f"{error.__class__.__name__}: {interaction.command.name}")
24+
await error.send()
3625
case _:
37-
self.logger.critical(
38-
f"{ctx.message.id}, {ctx.message.content} | {error}",
26+
logger.critical(error)
27+
await interaction.response.send_message(
28+
embed=ErrorMessage(
29+
"Tato zpráva by se nikdy zobrazit správně neměla. "
30+
"Jsi borec, že jsi mi dokázal rozbít Jáchyma, nechceš mi o tom napsat do issues na githubu?"
31+
)
3932
)
40-
print(error)
41-
return None
4233

43-
@commands.Cog.listener()
44-
async def on_command(self, ctx: commands.Context):
45-
self.logger.info(f"{ctx.message.id} {ctx.message.content}")
34+
35+
class PrettyError(CommandInvokeError):
36+
"""Pretty errors useful for raise keyword"""
37+
38+
def __init__(self, message: str, interaction: Interaction, inner_exception: Exception | None = None):
39+
super().__init__(interaction.command, inner_exception)
40+
self.message = message
41+
self.interaction = interaction
42+
43+
async def send(self):
44+
if not self.interaction.response.is_done():
45+
await self.interaction.response.send_message(embed=ErrorMessage(self.message))
46+
else:
47+
await self.interaction.followup.send(embed=ErrorMessage(self.message))
48+
49+
50+
class TooManyOptionsError(PrettyError):
51+
pass
52+
53+
54+
class TooFewOptionsError(PrettyError):
55+
pass
4656

4757

4858
async def setup(bot):

‎cogs/morserovka.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from discord import Message, app_commands
55
from discord.ext import commands
66

7-
87
# Klasická morseovka
98

109

@@ -81,10 +80,7 @@ async def zasifruj(self, interaction: discord.Interaction, message: str) -> Mess
8180
@app_commands.command(name="desifruj", description="Dešifruj text z morserovky!")
8281
@app_commands.describe(message="Věta nebo slovo pro dešifrování")
8382
async def desifruj(self, interaction: discord.Interaction, message: str) -> Message:
84-
decipher = "".join(
85-
self.REVERSED_MORSE_CODE_DICT.get(letter)
86-
for letter in re.split(r"\/|\\|\|", message)
87-
)
83+
decipher = "".join(self.REVERSED_MORSE_CODE_DICT.get(letter) for letter in re.split(r"\/|\\|\|", message))
8884
return await interaction.response.send_message(decipher)
8985

9086

‎cogs/poll_command.py

+38-33
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,51 @@
22

33
import discord
44
from discord import app_commands
5+
from discord.app_commands import Transform, Transformer
56
from discord.ext import commands
67
from loguru import logger
78

9+
from cogs.error import TooFewOptionsError, TooManyOptionsError
810
from src.db_folder.databases import PollDatabase, VoteButtonDatabase
911
from src.jachym import Jachym
1012
from src.ui.embeds import PollEmbed, PollEmbedBase
1113
from src.ui.poll import Poll
1214
from src.ui.poll_view import PollView
1315

1416

15-
def error_handling(answer: list[str]) -> str:
16-
if len(answer) > Poll.MAX_OPTIONS:
17-
return f"Zadal jsi příliš mnoho odpovědí, můžeš maximálně {Poll.MAX_OPTIONS}!"
18-
return f"Zadal jsi příliš málo odpovědí, můžeš alespoň {Poll.MIN_OPTIONS}!"
17+
class OptionsTransformer(Transformer):
18+
async def transform(
19+
self, interaction: discord.Interaction, option: str
20+
) -> TooManyOptionsError | TooFewOptionsError | list[str]:
21+
"""
22+
Transformer method to transformate a single string to multiple options. If they are not within parameters,
23+
raises an error, else returns options.
1924
25+
Parameters
26+
----------
27+
interaction: discord.Interaction
28+
option: str
29+
30+
Returns
31+
-------
32+
List of strings
33+
34+
Raises:
35+
-------
36+
TooManyOptionsError, TooFewOptionsError
37+
38+
"""
39+
answers = [option for option in re.split('"|"|“|„', option) if option.strip()]
40+
if len(answers) > Poll.MAX_OPTIONS:
41+
msg = f"Zadal jsi příliš mnoho odpovědí, můžeš maximálně {Poll.MAX_OPTIONS}!"
42+
raise TooManyOptionsError(msg, interaction)
43+
if len(answers) < Poll.MIN_OPTIONS:
44+
msg = f"Zadal jsi příliš málo odpovědí, můžeš alespoň {Poll.MIN_OPTIONS}!"
45+
raise TooFewOptionsError(msg, interaction)
46+
return answers
2047

21-
class PollCreate(commands.Cog):
22-
POLL_PARAMETERS = {
23-
"name": "anketa",
24-
"description": "Anketa pro hlasování. Jsou vidět všichni hlasovatelé.",
25-
"question": "Otázka, na kterou potřebuješ vědět odpověď",
26-
"answer": 'Odpovědi, rozděluješ odpovědi uvozovkou ("), maximálně pouze 10 možností',
27-
"help": """
28-
Jednoduchá anketa, která obsahuje otázku a odpovědi. Povoleno je 10 možností.
29-
""",
30-
}
31-
32-
# Bugfix for iPhone users who have different font for aposthrofe
33-
REGEX_PATTERN = ['"', "”", "“", "„"]
3448

49+
class PollCreate(commands.Cog):
3550
def __init__(self, bot: Jachym):
3651
self.bot = bot
3752

@@ -45,29 +60,19 @@ def __init__(self, bot: Jachym):
4560
answer='Odpovědi, rozděluješ odpovědi uvozovkou ("), maximálně pouze 10 možností',
4661
)
4762
async def pool(
48-
self,
49-
interaction: discord.Interaction,
50-
question: str,
51-
answer: str,
63+
self,
64+
interaction: discord.Interaction,
65+
question: str,
66+
answer: Transform[list[str, ...], OptionsTransformer],
5267
) -> discord.Message:
53-
await interaction.response.send_message(
54-
embed=PollEmbedBase("Dělám na tom, vydrž!"),
55-
)
68+
await interaction.response.send_message(embed=PollEmbedBase("Nahrávám anketu..."))
5669
message = await interaction.original_response()
5770

58-
answers = [
59-
answer
60-
for answer in re.split("|".join(self.REGEX_PATTERN), answer)
61-
if answer.strip()
62-
]
63-
if error_handling(answers):
64-
return await message.edit(embed=PollEmbedBase(error_handling(answers)))
65-
6671
poll = Poll(
6772
message_id=message.id,
6873
channel_id=message.channel.id,
6974
question=question,
70-
options=answers,
75+
options=answer,
7176
user_id=interaction.user.id,
7277
)
7378

‎cogs/sync_command.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ def __init__(self, bot: "Jachym"):
1616
@commands.guild_only()
1717
@commands.is_owner()
1818
async def sync(
19-
self,
20-
ctx: Context,
21-
guilds: Greedy[discord.Guild],
22-
spec: Literal["-", "*", "^"] | None = None,
19+
self,
20+
ctx: Context,
21+
guilds: Greedy[discord.Guild],
22+
spec: Literal["-", "*", "^"] | None = None,
2323
) -> None:
2424
"""
2525
A command to sync all slash commands to servers user requires. Works like this:

‎pyproject.toml

+19-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
[tool.ruff]
22

3-
select = ["ALL"]
4-
5-
ignore = [
6-
"D", # Ignore docstrings
7-
"E501", # Line too long, let Black handle this
8-
"ANN", # typing, let mypy handle tis
9-
"INP001" # Namespace package eeeeh
3+
select = [
4+
"E", # pycodestyle
5+
"F", # pyflakes
6+
"UP", # pyupgrade,
7+
"I", # isort
8+
"UP", # pyupgrade
9+
"ASYNC",
10+
"BLE", # Blind Exception
11+
"T20", # Found a print!
12+
"RET", # Unnecessary return
13+
"SIM", # Simplify
14+
]
15+
exclude = [
16+
"tests",
1017
]
1118

19+
line-length = 120
20+
21+
[tool.black]
22+
23+
line-length = 120

‎readme.md

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
r
21
<h1 align=center>
32
<img src="fotky/Jáchym.png" alt="Logo Jáchyma">
43
<br>

‎src/db_folder/databases.py

+11-13
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from collections.abc import AsyncIterator
2+
from typing import TYPE_CHECKING
23

34
import aiomysql
45
import discord.errors
56
from discord import Message
67
from loguru import logger
78

8-
from src.jachym import Jachym
9+
if TYPE_CHECKING:
10+
from src.jachym import Jachym
11+
912
from src.ui.poll import Poll
1013

1114

@@ -68,7 +71,7 @@ async def fetch_all_answers(self, message_id) -> list[str]:
6871
tuple_of_tuples_db = await self.fetch_all_values(sql, value)
6972
return [answer for tupl in tuple_of_tuples_db for answer in tupl]
7073

71-
async def fetch_all_polls(self, bot: Jachym) -> AsyncIterator[Poll and Message]:
74+
async def fetch_all_polls(self, bot: "Jachym") -> AsyncIterator[Poll and Message]:
7275
sql = "SELECT * FROM `Poll`"
7376
polls = await self.fetch_all_values(sql)
7477

@@ -102,26 +105,21 @@ def __init__(self, pool: aiomysql.pool.Pool):
102105

103106
async def add_options(self, discord_poll: Poll):
104107
sql = "INSERT INTO `VoteButtons`(message_id, answers) VALUES (%s, %s)"
105-
values = [
106-
(discord_poll.message_id, vote_option)
107-
for vote_option in discord_poll.options
108-
]
108+
values = [(discord_poll.message_id, vote_option) for vote_option in discord_poll.options]
109109
await self.commit_many_values(sql, values)
110110

111-
async def add_user(self, message_id: Poll, user: int, index: int):
111+
async def add_user(self, discord_poll: Poll, user: int, index: int):
112112
sql = "INSERT INTO `Answers`(message_id, vote_user, iter_index) VALUES (%s, %s, %s)"
113-
values = (message_id, user, index)
113+
values = (discord_poll.message_id, user, index)
114114
await self.commit_value(sql, values)
115115

116-
async def remove_user(self, message_id: Poll, user: int, index: int):
116+
async def remove_user(self, discord_poll: Poll, user: int, index: int):
117117
sql = "DELETE FROM `Answers` WHERE message_id = %s AND vote_user = %s AND iter_index = %s"
118-
value = (message_id, user, index)
118+
value = (discord_poll.message_id, user, index)
119119
await self.commit_value(sql, value)
120120

121121
async def fetch_all_users(self, poll: Poll, index: int) -> set[int]:
122-
sql = (
123-
"SELECT vote_user FROM `Answers` WHERE message_id = %s AND iter_index = %s"
124-
)
122+
sql = "SELECT vote_user FROM `Answers` WHERE message_id = %s AND iter_index = %s"
125123

126124
values = (poll.message_id, index)
127125
users_voted_for = await self.fetch_all_values(sql, values)

0 commit comments

Comments
 (0)
Failed to load comments.