Переделан подход к работе с ошибками

This commit is contained in:
serafim.urukov 2021-08-10 01:01:46 +03:00
parent 9e85748a82
commit 471cf43f60
3 changed files with 141 additions and 83 deletions

View File

@ -1,27 +1,12 @@
from functools import wraps
from werkzeug.exceptions import *
from manager import *
import flask
import logging
import models
import time
UNEXPECTED_ERROR_DICT = dict(
error=True,
desc='UnexpectedError'
)
STUDENT_NOT_FOUND_DICT = dict(
error=True,
desc='StudentNotFound'
)
STUDENT_ALREADY_AUTHED_DICT = dict(
error = True,
desc='StudentAlreadyAuthed'
)
ANOTHER_STUDENT_ALREADY_AUTHED_DICT = dict(
error = True,
desc='AnotherStudentAlreadyAuthed'
)
logging.basicConfig()
LOG = logging.getLogger(__name__)
LOG.setLevel(logging.DEBUG)
@ -60,22 +45,12 @@ def hello_world():
@app.route("/auth")
@availability
def auth():
token = flask.request.args.get("token")
tg_id = flask.request.args.get("tg_id")
try:
token = flask.request.args.get("token", None)
tg_id = flask.request.args.get("tg_id", None)
if token is None or tg_id is None:
raise BadRequest("Не предоставлены token и tg_id!")
student = app.db.auth_by_token(token, tg_id)
return flask.jsonify(dict(
student=student,
error=False
))
except StudentAlreadyAuthed:
return flask.jsonify(STUDENT_ALREADY_AUTHED_DICT)
except AnotherStudentAlreadyAuthed:
return flask.jsonify(ANOTHER_STUDENT_ALREADY_AUTHED_DICT)
except StudentNotFound:
return flask.jsonify(STUDENT_NOT_FOUND_DICT)
except:
return flask.jsonify(UNEXPECTED_ERROR_DICT)
return flask.jsonify(dict(student=student))
@app.route("/grades")
@ -83,24 +58,41 @@ def auth():
def grades():
"""Возвращает оценки по id пользователя из телеграма."""
tg_id = flask.request.args.get("tg_id")
try:
student = app.db.get_student_by_tg_id(tg_id)
app.db.set_assignments_for_student(student)
return flask.jsonify(
dict(
error=False,
score=student.score,
grade=student.grade,
assignments=student.assignments,
)
)
except StudentNotFound:
return flask.jsonify(STUDENT_NOT_FOUND_DICT)
except:
return flask.jsonify(UNEXPECTED_ERROR_DICT)
@app.errorhandler(StudentAlreadyAuthed)
def show_error(err):
return flask.jsonify({"error": type(err).__name__, "message": err.message}), 409
@app.errorhandler(AnotherStudentAlreadyAuthed)
def show_error(err):
return flask.jsonify({"error": type(err).__name__, "message": err.message}), 403
@app.errorhandler(StudentNotFound)
def show_error(err):
return flask.jsonify({"error": type(err).__name__, "message": err.message}), 404
@app.errorhandler(BadRequest)
def show_error(err):
return flask.jsonify({"error": type(err).__name__, "message": err.description}), 400
# Любая другая ошибка вылетит вот здесь
@app.errorhandler(Exception)
def show_error(err):
return flask.jsonify({"error": type(err).__name__, "message": str(err)}), 500
if __name__ == "__main__":

View File

@ -1,5 +1,7 @@
import logging
from typing import Any, Optional
import requests
import time
from pathlib import Path
@ -18,21 +20,82 @@ from messages import Messages
TOKEN = Path(".secrets/bot_token.txt").read_text()
MESSAGES = Messages()
HOST = "http://carolyn-spreadsheets:5000"
"""ID канала где собираются логи"""
CHAT_LOG_ID = -507530583
class TgLogger(logging.Logger):
"""Обертка на логгер, чтобы можно было использовать обычные лог-функции,
но при этом получать логи в телеграм чат.
Есть баг, который не позволяет вызывать лог с ленивыми аргументами. Если
вызывать
```python
LOG.warning("%s", var)
```
, то можно получить такую ошибку:
```python
TypeError: _log_to_telegram() got multiple values for argument 'msg'
```
Поэтому используйте,
```
LOG.warning(f"{var}")
```
"""
def __init__(self, name, level=logging.NOTSET):
self._bot = None
self._chat_log_id = CHAT_LOG_ID
return super(TgLogger, self).__init__(name, level)
@property
def bot(self):
return self._bot
@bot.setter
def bot(self, value):
self._bot = value
def _log_to_telegram(
self,
msg: str,
*args,
emoji: str,
):
if self._bot is not None:
txt = f"{emoji} {msg};"
self._bot.send_message(chat_id=CHAT_LOG_ID, text=txt)
def warning(self, msg: Any, *args: Any, **kwargs: Any) -> None:
self._log_to_telegram(msg=msg, emoji="", *args)
return super().warning(
msg,
*args,
**kwargs,
)
def error(self, msg: Any, *args: Any, **kwargs: Any) -> None:
self._log_to_telegram(msg=msg, emoji="💢", *args)
return super().error(
msg,
*args,
**kwargs,
)
def exception(self, msg, *args: Any, **kwargs: Any) -> None:
self._log_to_telegram(msg=msg, emoji="💥", *args)
return super().exception(msg, *args, **kwargs)
updater = Updater(token=TOKEN, use_context=True)
dispatcher = updater.dispatcher
logging.setLoggerClass(TgLogger)
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
)
LOG = logging.getLogger(__name__)
LOG.debug("Token is %s", TOKEN)
updater = Updater(token=TOKEN, use_context=True)
dispatcher = updater.dispatcher
def log_to_group_chat(update: Update, text: str):
update.message.bot.send_message(chat_id=CHAT_LOG_ID, text=text)
LOG.bot = updater.bot
# ================================================================ BOT COMMANDS
@ -41,45 +104,38 @@ def start(update: Update, context: CallbackContext):
"""Стартовое сообщение (к команде /start)"""
user_id = update.message.from_user.id
username = update.message.from_user.username
student_token = context.args[0] if len(context.args) > 0 else None
p = context.args[0] if len(context.args) > 0 else None
if p is None:
if student_token is None:
update.message.reply_markdown_v2(text=MESSAGES.auth.NO_AUTH)
return
# Длинная операция, сообщим о запущенном процессе.
update.message.reply_text(text=MESSAGES.auth.START)
LOG.info(f"Authenticanting {user_id}")
try:
data = requests.get(
req = requests.get(
f"{HOST}/auth",
params={"token": p, "tg_id": user_id},
).json()
except requests.exceptions.ConnectionError:
log_to_group_chat(update,
text=MESSAGES.auth.failure_log(
{'desc':'ConnectionError'})
params={"token": student_token, "tg_id": user_id},
)
data = req.json()
except requests.exceptions.RequestException:
LOG.exception("@{username} вызвал: Ошибка подключения к сервису Spreadsheets.")
update.message.reply_sticker(MESSAGES.stickers.DEAD)
return
if data['error']:
log_to_group_chat(update,
text=MESSAGES.auth.failure_log(data)
)
update.message.reply_text(
text=MESSAGES.auth.failure(data)
)
if req.status_code != 200:
LOG.error(f"Ошибка авторизации, у пользователя @{username} {data}")
err = data.get("error", "")
update.message.reply_text(text=MESSAGES.auth.failure(err))
update.message.reply_sticker(MESSAGES.stickers.DEAD)
return
log_to_group_chat(update,
text=MESSAGES.auth.success_log(data['student'])
)
update.message.reply_text(
text=MESSAGES.auth.hello(data['student']),
text=MESSAGES.auth.hello(data["student"]),
)
@ -87,6 +143,10 @@ def grades(update: Update, context: CallbackContext):
"""Команда: Получить оценки студента."""
user_id = update.message.from_user.id
start = time.monotonic()
# Длинная операция, сообщим о запущенном процессе.
look_msg = update.message.reply_html(MESSAGES.score.START)
try:
data = requests.get(f"{HOST}/grades", params={"tg_id": user_id}).json()
score = data["score"]
@ -99,6 +159,8 @@ def grades(update: Update, context: CallbackContext):
except:
LOG.exception("Failed to get grades.")
update.message.reply_sticker(MESSAGES.stickers.DEAD)
diff = time.monotonic() - start
look_msg.edit_text(MESSAGES.score.timeit(diff))
def echo(update: Update, context: CallbackContext):

View File

@ -11,15 +11,13 @@ class Messages:
def hello(self, data):
return f"Привет, {data['name']} 👋"
def failure(self, data):
if data['desc'] == 'StudentNotFound':
return "⛔ Ссылка больше не работает"
elif data['desc'] == 'StudentAlreadyAuthed':
return "⛔ Регистрация по этой ссылке уже прошла"
elif data['desc'] == 'AnotherStudentAlreadyAuthed':
return "😨 Кто-то другой уже зерегистрировался по этой ссылке"
else:
return "😟 Возникли неполадки"
def failure(self, err: str):
cases = {
"StudentNotFound": "⛔ Ссылка больше не работает",
"StudentAlreadyAuthed": "⛔ Регистрация по этой ссылке уже прошла",
"AnotherStudentAlreadyAuthed": "😨 Кто-то другой уже зерегистрировался по этой ссылке",
}
return cases.get(err, "😟 Возникли неполадки")
def success_log(self, data):
return f"✅ AUTH: {data['name']} "
@ -28,6 +26,8 @@ class Messages:
return f"🟥 AUTH: {data['desc']}"
class Score:
START = "👀 Посмотрим (~3с) ..."
def get(self, grade, score, n_of_assignments):
summary = (
f"Ранг: <b>{score:.2f}</b> {n_of_assignments} зад.\n"
@ -35,6 +35,10 @@ class Messages:
)
return summary
@staticmethod
def timeit(t: float):
return f"⏱ Посмотрели за {t:.2f}с"
class Spreadsheets:
def __init__(self):
self.DEAD = "⚰ Сервис «таблицы» недоступен"