Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/add_message_history #336

Open
wants to merge 25 commits into
base: develop
Choose a base branch
from
Open

Conversation

vomerf
Copy link
Contributor

@vomerf vomerf commented Apr 24, 2023

Нужна помощь или подсказка. Получается сохранять данные об отправленных сообщениях в БД, но иногда вылазить такое предупреждение:
sys:1: SAWarning: The garbage collector is trying to clean up non-checked-in connection <AdaptedConnection
<asyncpg.connection.Connection object at 0x00000188D3A96EA0>>, which will be terminated. Please ensure that
SQLAlchemy pooled connections are returned to the pool explicitly, either by calling close()
or by using appropriate context managers to manage their lifecycle

@vomerf vomerf temporarily deployed to healthcheck April 24, 2023 07:19 — with GitHub Actions Inactive
@vomerf vomerf requested a review from ADesBiysk April 24, 2023 07:19
Copy link
Member

@ADesBiysk ADesBiysk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Честно говоря, не вижу проблемы.
Создал тестовый эндпойнт и нормально отработало.
Так же и в боте.

async def get_history_service(sessions):
    async for session in sessions:  # noqa R503
        history_repository = MessageHistoryRepository(session)
        shift_repository = ShiftRepository(session)
        return MessageHistoryService(history_repository, shift_repository)

async def send_daily_task_job(context: CallbackContext) -> None:
    history_session = get_session()
    history_service = await get_history_service(history_session)
    await history_service.create_history_message(
        user_id="36a9496b-21fc-4e69-b071-bfcc61d1821f",
        chat_id=123,
        message="Test",
        status=MessageHistory.Status.STATUS_OF_CHECKED_TASK
    )

Ругается на отсутствие таблицы и статуса. Если добавить и применить миграцию, всё будет работать.

class MessageHistory(Base):
"""Хрениние истории отправленных сообщений."""

class Status(str, enum.Enum):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь не статус, а событие Event. Он нужен для последующей сортировки по типу события.


REGISTRATION = "registration"
GET_TASK = "get_task"
REPORT_MENTION = "report_mention"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Именование статусов потом нужно будет пересмотреть. Не все удачные.

__tablename__ = 'message_history'

user_id = Column(UUID(as_uuid=True), ForeignKey(User.id), nullable=False)
message = Column(Text, nullable=False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше String. Нужно посмотреть максимальную длину наших сообщений и сделать в 2 раза больше. Этого будет достаточно.

Enum(Status, name="message_history_status", values_callable=lambda obj: [e.value for e in obj]),
nullable=False,
)
shift_id = Column(UUID(as_uuid=True), ForeignKey(Shift.id), nullable=False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Нужно добавить метод repr

@vomerf vomerf temporarily deployed to healthcheck April 25, 2023 11:18 — with GitHub Actions Inactive
@ADesBiysk
Copy link
Member

НОВЫЙ КОММЕНТАРИЙ. В handlers добавил создания сессии увидел где-то, что для каждой создается свой, возможно не правильно и достаточно одного. Получил объект класса вызвал метод для сохранения и тут он сохранил. Такие вопросы у меня.1 - В handler для каждой функции так надо прописывать получение сессии потом получение объекта потому что как я вижу в других функциях register_user, update_user_data и web_app_data там вызывается толи обновление то ли отправка нового сообщения "пример" await update.message.reply_text(f"Ошибка при заполнении данных:\n{e}") это тоже надо сохранять? 2 - у меня никак не получается сделать так чтобы после вызова метода send_message сохранялось сообщение я там оставил закомиченный код как я пытался это сделать.

  1. Все сообщения нужно отправлять только одной функцией, и ещё одной - фотографии. Тогда код сохранения истории сообщений нужно будет добавить в этих функциях.
  2. Лучше сразу приводить ошибку, которая возникает. Если пытаться воспроизводить все ошибки, тратится очень много времени.

Copy link
Member

@ADesBiysk ADesBiysk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В текущем варианте запись в БД создаётся, но нужно при создании экземпляра MessageHistory передавать параметры в правильной последовательности. И в shift_id я просто передал id реально существующей смены, поскольку когда человек получает стартовое сообщение активной смены может ещё не быть.

user = await user_service.get_user_by_telegram_id(update.effective_chat.id)
context.user_data["user"] = user
if user and user.telegram_blocked:
await user_service.unset_telegram_blocked(user)
await context.bot.send_message(chat_id=update.effective_chat.id, text=start_text)
if user:
await history_service.create_history_message(
user.id, update.effective_chat.id, start_text, status="registration"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь статус надо подставлять в виде MessageHistory.Status.REGISTRATION. Хотя писал в прошлый раз, что не статус а событие.

@@ -58,3 +60,11 @@ async def get_shift_service_callback(sessions):
shift_repository, task_service, report_repository, user_repository, request_repository
)
return shift_service


async def get_history_service_callback(sessions):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь callback не нужен в названии. У других функций потом тоже уберём.

@ADesBiysk
Copy link
Member

НОВЫЙ КОММЕНТАРИЙ. В handlers добавил создания сессии увидел где-то, что для каждой создается свой, возможно не правильно и достаточно одного. Получил объект класса вызвал метод для сохранения и тут он сохранил. Такие вопросы у меня.1 - В handler для каждой функции так надо прописывать получение сессии потом получение объекта потому что как я вижу в других функциях register_user, update_user_data и web_app_data там вызывается толи обновление то ли отправка нового сообщения "пример" await update.message.reply_text(f"Ошибка при заполнении данных:\n{e}") это тоже надо сохранять? 2 - у меня никак не получается сделать так чтобы после вызова метода send_message сохранялось сообщение я там оставил закомиченный код как я пытался это сделать.

  1. Да. Тоже нужно сохранять. Эти сообщения тоже надо отправлять через нашу функцию. Но, это можно исправить, когда уже всё будет работать с другими сообщениями.
  2. Нужно в инициализатор бота передавать, кроме Application, экземпляр MessageHistoryService.

@vomerf vomerf temporarily deployed to healthcheck May 3, 2023 08:12 — with GitHub Actions Inactive
@@ -66,8 +69,14 @@ async def start(update: Update, context: CallbackContext) -> None:
)
return
await update_user_data(update, context)
# не знаю как сюда попасть чтобы проверить
await history_service.create_history_message(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Сюда можно попасть, если принять участие в одной смене и потом подавать заявку на участие в новой смене.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Путаница, где-то event, а ниже status.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Так неправильно передавать значение status='update_data', лучше так MessageHistory.Event.REGISTRATION.value


@check_user_blocked
@retry()
async def send_message(self, user: models.User, text: str) -> None:
async def send_message(self, user: models.User, text: str, event: str) -> None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Event не строка, а enum из модели.


user_id = Column(UUID(as_uuid=True), ForeignKey(User.id))
message = Column(String(400), nullable=False)
chat_id = Column(BigInteger, nullable=False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Зачем нам chat_id? Он же совпадает с user.telegram_id. Нам нужно сохранять message_id, чтобы позже можно было получить доступ к этому сообщению.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

что подразумевается под message_id это primary key данной таблицы или надо сообщение выносить в отдельную таблицу?

@vomerf vomerf temporarily deployed to healthcheck May 5, 2023 12:41 — with GitHub Actions Inactive
src/bot/jobs.py Outdated
@@ -71,6 +79,7 @@ async def send_daily_task_job(context: CallbackContext) -> None:
f"Не забудь сделать фотографию, как ты выполняешь задание, и отправить на проверку."
),
DAILY_TASK_BUTTONS,
event,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Подставляй событие сразу сюда. Иначе, приходится возвращаться назад, чтобы узнать, какое событие будет.


async def create_history_message(self, user_id, message_id, message, event) -> None:
try:
shift = await self.__shift_repository.get_started_shift_id()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше смену передавать в качестве параметра. Сейчас, и так не оптимальный вариант сохранения истории. Каждое сообщение сохраняется отдельным запросом в БД. А так добавляется ещё один запрос, для получения смены, т.е. 2 запроса на каждое сообщение. :(

@vomerf vomerf temporarily deployed to healthcheck May 16, 2023 10:42 — with GitHub Actions Inactive
Copy link
Member

@ADesBiysk ADesBiysk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

По ошибке, не вижу очевидных проблем. Попробую завтра поэкспериментировать.

else:
await register_user(update, context)
await history_service.create_history_message(user, message.message_id, start_text, event)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь точно user = None

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это я знаю поэтому и написал user, а не user.id или как-то по другому надо, немного не понял если честно. Сделать как со сменой если не передать юзера то по умолчанию None ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если пользователя нет, то и сообщения сохранять нет смысла.
Здесь, после регистрации пользователь уже есть. Его надо получить и использовать при сохранении.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут получается что функция register только форму с регистрацией кидает, но еще пользователя в базу не сохраняет, это как-то пометить надо будет в event? событие перед формой регистрации?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

я убрал и после update и после register, я написал вызов в функцию web_app_data там как я понял и обновление данных обрабатывается и регистрация

src/bot/jobs.py Outdated
members = await member_service.get_members_with_no_reports()
if members:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Поле shift_id обязательное у модели Member, поэтому эта проверка не имеет смысла. Можно ниже просто подставлять member.shift_id.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Поле у Member обязательное, а если список пустой будет то ошибка вылетит. Как раз и проверяю, что список не пустой такое наверно маловероятно, но наверное возможно

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Всё просто. Если в members придёт пустой список, то генератор списка также вернёт пустой список. А если не пустой, то смену возьмёт из member, как я написал выше.

    send_message_tasks = [
        bot_service.send_message(
            member.user,
            (
                f"{member.user.name} {member.user.surname}, мы потеряли тебя! "
                f"Задание все еще ждет тебя. "
                f"Напоминаем, что за каждое выполненное задание ты получаешь виртуальные "
                f"\"ломбарьерчики\", которые можешь обменять на призы и подарки!"
            ),
            MessageHistory.Event.REPORT_MENTION,
            member.shift_id,
        )
        for member in members
    ]

Так точно ошибки не будет.
Твой вариант будет совсем немного быстрее, но менее очевиден. Возникает вопрос, почему смену выделили отдельно и именно у первого элемента списка. Это какой-то особый случай... Непонятно.

src/bot/jobs.py Outdated
await report_service.set_status_to_waiting_reports(Report.Status.SKIPPED)
await member_service.exclude_lagging_members(context.application)
task, members = await report_service.get_today_task_and_active_members(date.today().day)
await report_service.create_daily_reports(members, task)
task_photo = urljoin(settings.APPLICATION_URL, task.url)
if members:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь так же.


async def create_history_message(self, user_id, message_id, message, event, shift_id=None) -> None:
history_message = MessageHistory(
user_id=user_id, message_id=message_id, message=message, event=event, shift_id=shift_id
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше позиционно передать аргументы.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если я их делаю не позиционными, то у меня с ошибкой падает и говорит, что я передаю 6 аргументов, а он хочет всего один

if user.telegram_blocked:
return
chat_id = user.telegram_id
try:
await self.__bot.send_message(chat_id=chat_id, text=text)
# Тут я хотел написать код который записывал бы в БД после отправки сообщения
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вот тут у меня самая главная проблема я не могу вызвать это метод чтобы сохранить сообщение, пытался сделать как в закоментированном коде, но так не получается сделать, пытался добавить атрабутом объект класса таким образовм history_service: MessageHistoryService = Depends() так тоже не получается


user_id = Column(UUID(as_uuid=True), ForeignKey(User.id))
message = Column(String(400), nullable=False)
chat_id = Column(BigInteger, nullable=False)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

что подразумевается под message_id это primary key данной таблицы или надо сообщение выносить в отдельную таблицу?

src/bot/jobs.py Outdated
members = await member_service.get_members_with_no_reports()
if members:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Поле у Member обязательное, а если список пустой будет то ошибка вылетит. Как раз и проверяю, что список не пустой такое наверно маловероятно, но наверное возможно

else:
await register_user(update, context)
await history_service.create_history_message(user, message.message_id, start_text, event)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это я знаю поэтому и написал user, а не user.id или как-то по другому надо, немного не понял если честно. Сделать как со сменой если не передать юзера то по умолчанию None ?

@vomerf vomerf temporarily deployed to healthcheck May 22, 2023 12:37 — with GitHub Actions Inactive
@vomerf vomerf temporarily deployed to healthcheck June 3, 2023 06:55 — with GitHub Actions Inactive
@vomerf vomerf temporarily deployed to healthcheck June 3, 2023 07:21 — with GitHub Actions Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants