Skip to content

Commit

Permalink
Error handling (#18)
Browse files Browse the repository at this point in the history
## 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
  • Loading branch information
TheXer authored Jun 30, 2023
2 parents 8a2cf62 + 965bffa commit d999fa1
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 134 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
on: push

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v1

- name: Set up CPython
uses: actions/setup-python@v4

- name: Install dependencies
id: install-deps
run: |
python -m pip install --upgrade pip setuptools wheel ruff
- name: Black format
uses: psf/black@stable

- name: Ruff Check
run: ruff check .



72 changes: 41 additions & 31 deletions cogs/error.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,58 @@
import logging

from discord import Interaction
from discord.app_commands import CommandInvokeError
from discord.ext import commands
from loguru import logger

from src.ui.embeds import ErrorMessage


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

def __init__(self, bot):
self.bot = bot

self.logger = logging.getLogger("discord")
handler = logging.FileHandler(
filename="discord.log",
encoding="utf-8",
mode="w",
)
handler.setFormatter(
logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s"),
)
self.logger.addHandler(handler)
tree = self.bot.tree
self._old_tree_error = tree.on_error
tree.on_error = self.on_app_command_error

@commands.Cog.listener()
async def on_command_error(
self,
ctx: commands.Context,
error: commands.CommandError,
):
async def on_app_command_error(self, interaction: Interaction, error: Exception):
match error:
case commands.MissingPermissions():
return await ctx.send("Chybí ti požadovaná práva!")

case commands.CommandNotFound():
return None

case PrettyError():
# if I use only 'error', gives me NoneType. Solved by this
logger.error(f"{error.__class__.__name__}: {interaction.command.name}")
await error.send()
case _:
self.logger.critical(
f"{ctx.message.id}, {ctx.message.content} | {error}",
logger.critical(error)
await interaction.response.send_message(
embed=ErrorMessage(
"Tato zpráva by se nikdy zobrazit správně neměla. "
"Jsi borec, že jsi mi dokázal rozbít Jáchyma, nechceš mi o tom napsat do issues na githubu?"
)
)
print(error)
return None

@commands.Cog.listener()
async def on_command(self, ctx: commands.Context):
self.logger.info(f"{ctx.message.id} {ctx.message.content}")

class PrettyError(CommandInvokeError):
"""Pretty errors useful for raise keyword"""

def __init__(self, message: str, interaction: Interaction, inner_exception: Exception | None = None):
super().__init__(interaction.command, inner_exception)
self.message = message
self.interaction = interaction

async def send(self):
if not self.interaction.response.is_done():
await self.interaction.response.send_message(embed=ErrorMessage(self.message))
else:
await self.interaction.followup.send(embed=ErrorMessage(self.message))


class TooManyOptionsError(PrettyError):
pass


class TooFewOptionsError(PrettyError):
pass


async def setup(bot):
Expand Down
6 changes: 1 addition & 5 deletions cogs/morserovka.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from discord import Message, app_commands
from discord.ext import commands


# Klasická morseovka


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


Expand Down
71 changes: 38 additions & 33 deletions cogs/poll_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,51 @@

import discord
from discord import app_commands
from discord.app_commands import Transform, Transformer
from discord.ext import commands
from loguru import logger

from cogs.error import TooFewOptionsError, TooManyOptionsError
from src.db_folder.databases import PollDatabase, VoteButtonDatabase
from src.jachym import Jachym
from src.ui.embeds import PollEmbed, PollEmbedBase
from src.ui.poll import Poll
from src.ui.poll_view import PollView


def error_handling(answer: list[str]) -> str:
if len(answer) > Poll.MAX_OPTIONS:
return f"Zadal jsi příliš mnoho odpovědí, můžeš maximálně {Poll.MAX_OPTIONS}!"
return f"Zadal jsi příliš málo odpovědí, můžeš alespoň {Poll.MIN_OPTIONS}!"
class OptionsTransformer(Transformer):
async def transform(
self, interaction: discord.Interaction, option: str
) -> TooManyOptionsError | TooFewOptionsError | list[str]:
"""
Transformer method to transformate a single string to multiple options. If they are not within parameters,
raises an error, else returns options.
Parameters
----------
interaction: discord.Interaction
option: str
Returns
-------
List of strings
Raises:
-------
TooManyOptionsError, TooFewOptionsError
"""
answers = [option for option in re.split('"|"|“|„', option) if option.strip()]
if len(answers) > Poll.MAX_OPTIONS:
msg = f"Zadal jsi příliš mnoho odpovědí, můžeš maximálně {Poll.MAX_OPTIONS}!"
raise TooManyOptionsError(msg, interaction)
if len(answers) < Poll.MIN_OPTIONS:
msg = f"Zadal jsi příliš málo odpovědí, můžeš alespoň {Poll.MIN_OPTIONS}!"
raise TooFewOptionsError(msg, interaction)
return answers

class PollCreate(commands.Cog):
POLL_PARAMETERS = {
"name": "anketa",
"description": "Anketa pro hlasování. Jsou vidět všichni hlasovatelé.",
"question": "Otázka, na kterou potřebuješ vědět odpověď",
"answer": 'Odpovědi, rozděluješ odpovědi uvozovkou ("), maximálně pouze 10 možností',
"help": """
Jednoduchá anketa, která obsahuje otázku a odpovědi. Povoleno je 10 možností.
""",
}

# Bugfix for iPhone users who have different font for aposthrofe
REGEX_PATTERN = ['"', "”", "“", "„"]

class PollCreate(commands.Cog):
def __init__(self, bot: Jachym):
self.bot = bot

Expand All @@ -45,29 +60,19 @@ def __init__(self, bot: Jachym):
answer='Odpovědi, rozděluješ odpovědi uvozovkou ("), maximálně pouze 10 možností',
)
async def pool(
self,
interaction: discord.Interaction,
question: str,
answer: str,
self,
interaction: discord.Interaction,
question: str,
answer: Transform[list[str, ...], OptionsTransformer],
) -> discord.Message:
await interaction.response.send_message(
embed=PollEmbedBase("Dělám na tom, vydrž!"),
)
await interaction.response.send_message(embed=PollEmbedBase("Nahrávám anketu..."))
message = await interaction.original_response()

answers = [
answer
for answer in re.split("|".join(self.REGEX_PATTERN), answer)
if answer.strip()
]
if error_handling(answers):
return await message.edit(embed=PollEmbedBase(error_handling(answers)))

poll = Poll(
message_id=message.id,
channel_id=message.channel.id,
question=question,
options=answers,
options=answer,
user_id=interaction.user.id,
)

Expand Down
8 changes: 4 additions & 4 deletions cogs/sync_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ def __init__(self, bot: "Jachym"):
@commands.guild_only()
@commands.is_owner()
async def sync(
self,
ctx: Context,
guilds: Greedy[discord.Guild],
spec: Literal["-", "*", "^"] | None = None,
self,
ctx: Context,
guilds: Greedy[discord.Guild],
spec: Literal["-", "*", "^"] | None = None,
) -> None:
"""
A command to sync all slash commands to servers user requires. Works like this:
Expand Down
26 changes: 19 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
[tool.ruff]

select = ["ALL"]

ignore = [
"D", # Ignore docstrings
"E501", # Line too long, let Black handle this
"ANN", # typing, let mypy handle tis
"INP001" # Namespace package eeeeh
select = [
"E", # pycodestyle
"F", # pyflakes
"UP", # pyupgrade,
"I", # isort
"UP", # pyupgrade
"ASYNC",
"BLE", # Blind Exception
"T20", # Found a print!
"RET", # Unnecessary return
"SIM", # Simplify
]
exclude = [
"tests",
]

line-length = 120

[tool.black]

line-length = 120
1 change: 0 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
r
<h1 align=center>
<img src="fotky/Jáchym.png" alt="Logo Jáchyma">
<br>
Expand Down
24 changes: 11 additions & 13 deletions src/db_folder/databases.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
from collections.abc import AsyncIterator
from typing import TYPE_CHECKING

import aiomysql
import discord.errors
from discord import Message
from loguru import logger

from src.jachym import Jachym
if TYPE_CHECKING:
from src.jachym import Jachym

from src.ui.poll import Poll


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

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

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

async def add_options(self, discord_poll: Poll):
sql = "INSERT INTO `VoteButtons`(message_id, answers) VALUES (%s, %s)"
values = [
(discord_poll.message_id, vote_option)
for vote_option in discord_poll.options
]
values = [(discord_poll.message_id, vote_option) for vote_option in discord_poll.options]
await self.commit_many_values(sql, values)

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

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

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

values = (poll.message_id, index)
users_voted_for = await self.fetch_all_values(sql, values)
Expand Down
Loading

0 comments on commit d999fa1

Please sign in to comment.