From 251aa1d76a64ec4fb8e7da0ec9c5c77a796c49bc Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Fri, 18 Oct 2019 13:05:39 +0200 Subject: [PATCH] Create a session for each individual command call --- royalnet/bots/discord.py | 2 +- royalnet/bots/generic.py | 3 + royalnet/bots/telegram.py | 2 +- royalnet/commands/commanddata.py | 8 ++ royalnet/commands/commandinterface.py | 6 +- royalnet/packs/royal/commands/diario.py | 20 ++-- royalnet/packs/royal/commands/mm.py | 115 +++++++++++----------- royalnet/packs/royal/commands/reminder.py | 16 +-- royalnet/packs/royal/commands/trivia.py | 6 +- 9 files changed, 95 insertions(+), 83 deletions(-) diff --git a/royalnet/bots/discord.py b/royalnet/bots/discord.py index b5aa86bc..d1637c49 100644 --- a/royalnet/bots/discord.py +++ b/royalnet/bots/discord.py @@ -38,7 +38,7 @@ class DiscordBot(GenericBot): # noinspection PyMethodParameters,PyAbstractClass class DiscordData(CommandData): def __init__(data, interface: CommandInterface, message: discord.Message): - data._interface = interface + super().__init__(interface) data.message = message async def reply(data, text: str): diff --git a/royalnet/bots/generic.py b/royalnet/bots/generic.py index 91bd3c0c..1ec70ad0 100644 --- a/royalnet/bots/generic.py +++ b/royalnet/bots/generic.py @@ -35,6 +35,9 @@ class GenericBot: command = SelectedCommand(interface) except Exception as e: log.error(f"{e.__class__.__qualname__} during the registration of {SelectedCommand.__qualname__}") + continue + # Linking the command to the interface + interface.command = command # Override the main command name, but warn if it's overriding something if f"{interface.prefix}{SelectedCommand.name}" in self.commands: log.warning(f"Overriding (already defined): {SelectedCommand.__qualname__} -> {interface.prefix}{SelectedCommand.name}") diff --git a/royalnet/bots/telegram.py b/royalnet/bots/telegram.py index 1e918341..3cb2b9f1 100644 --- a/royalnet/bots/telegram.py +++ b/royalnet/bots/telegram.py @@ -55,7 +55,7 @@ class TelegramBot(GenericBot): # noinspection PyMethodParameters,PyAbstractClass class TelegramData(CommandData): def __init__(data, interface: CommandInterface, update: telegram.Update): - data.interface = interface + super().__init__(interface) data.update = update async def reply(data, text: str): diff --git a/royalnet/commands/commanddata.py b/royalnet/commands/commanddata.py index 05a7ab6a..d5f50953 100644 --- a/royalnet/commands/commanddata.py +++ b/royalnet/commands/commanddata.py @@ -1,9 +1,17 @@ import typing import warnings from .commanderrors import UnsupportedError +from .commandinterface import CommandInterface class CommandData: + def __init__(self, interface: CommandInterface): + self._interface: CommandInterface = interface + if len(self._interface.command.tables) > 0: + self.session = self._interface.alchemy.Session() + else: + self.session = None + async def reply(self, text: str) -> None: """Send a text message to the channel where the call was made. diff --git a/royalnet/commands/commandinterface.py b/royalnet/commands/commandinterface.py index 331c366c..fa539fb6 100644 --- a/royalnet/commands/commandinterface.py +++ b/royalnet/commands/commandinterface.py @@ -2,6 +2,7 @@ import typing import asyncio from .commanderrors import UnsupportedError if typing.TYPE_CHECKING: + from .command import Command from ..database import Alchemy from ..bots import GenericBot @@ -14,10 +15,7 @@ class CommandInterface: loop: asyncio.AbstractEventLoop = NotImplemented def __init__(self): - if self.alchemy: - self.session = self.alchemy.Session() - else: - self.session = None + self.command: typing.Optional[Command] = None # Will be bound after the command has been created def register_net_handler(self, message_type: str, network_handler: typing.Callable): """Register a new handler for messages received through Royalnet.""" diff --git a/royalnet/packs/royal/commands/diario.py b/royalnet/packs/royal/commands/diario.py index a9134e55..d8726553 100644 --- a/royalnet/packs/royal/commands/diario.py +++ b/royalnet/packs/royal/commands/diario.py @@ -74,7 +74,7 @@ class DiarioCommand(Command): if not (text or media_url): raise InvalidInputError("Missing text.") # Find the Royalnet account associated with the sender - quoted_tg = await asyncify(self.interface.session.query(self.interface.alchemy.Telegram) + quoted_tg = await asyncify(data.session.query(self.interface.alchemy.Telegram) .filter_by(tg_id=reply.from_user.id) .one_or_none) quoted_account = quoted_tg.royal if quoted_tg is not None else None @@ -124,8 +124,8 @@ class DiarioCommand(Command): # Find if there's a Royalnet account associated with the quoted name if quoted is not None: quoted_alias = await asyncify( - self.interface.session.query(self.interface.alchemy.Alias) - .filter_by(alias=quoted.lower()).one_or_none) + data.session.query(self.interface.alchemy.Alias) + .filter_by(alias=quoted.lower()).one_or_none) else: quoted_alias = None quoted_account = quoted_alias.royal if quoted_alias is not None else None @@ -147,8 +147,8 @@ class DiarioCommand(Command): timestamp=timestamp, media_url=media_url, spoiler=spoiler) - self.interface.session.add(diario) - await asyncify(self.interface.session.commit) + data.session.add(diario) + await asyncify(data.session.commit) await data.reply(f"✅ {str(diario)}") else: # Find the creator of the quotes @@ -182,9 +182,9 @@ class DiarioCommand(Command): # Find if there's a Royalnet account associated with the quoted name if quoted is not None: quoted_alias = await asyncify( - self.interface.session.query(self.interface.alchemy.Alias) - .filter_by(alias=quoted.lower()) - .one_or_none) + data.session.query(self.interface.alchemy.Alias) + .filter_by(alias=quoted.lower()) + .one_or_none) else: quoted_alias = None quoted_account = quoted_alias.royal if quoted_alias is not None else None @@ -201,6 +201,6 @@ class DiarioCommand(Command): timestamp=timestamp, media_url=None, spoiler=spoiler) - self.interface.session.add(diario) - await asyncify(self.interface.session.commit) + data.session.add(diario) + await asyncify(data.session.commit) await data.reply(f"✅ {str(diario)}") diff --git a/royalnet/packs/royal/commands/mm.py b/royalnet/packs/royal/commands/mm.py index bad5e4af..dcf78fd4 100644 --- a/royalnet/packs/royal/commands/mm.py +++ b/royalnet/packs/royal/commands/mm.py @@ -9,7 +9,6 @@ from royalnet.commands import * from royalnet.utils import asyncify, telegram_escape, sleep_until from ..tables import MMEvent, MMDecision, MMResponse - log = logging.getLogger(__name__) @@ -91,7 +90,7 @@ class MmCommand(Command): text += f"❌ {mmresponse.royal}\n" return text - async def _run_mm(self, mmevent: MMEvent) -> None: + async def _run_mm(self, mmevent: MMEvent, session) -> None: client: telegram.Bot = self.interface.bot.client async def update_message() -> None: @@ -107,61 +106,62 @@ class MmCommand(Command): pass decision_string = f"⚫️ Hai detto che forse parteciperai a [b]{mmevent.title}[/b]" \ - f" alle [b]{mmevent.datetime.strftime('%H:%M')}[/b].\n" \ - f"Confermi di volerci essere? (Metti sì anche se arrivi un po' in ritardo!)" + f" alle [b]{mmevent.datetime.strftime('%H:%M')}[/b].\n" \ + f"Confermi di volerci essere? (Metti sì anche se arrivi un po' in ritardo!)" decision_keyboard = telegram.InlineKeyboardMarkup([ - [telegram.InlineKeyboardButton("🔵 Ci sarò!", callback_data=f"mm_{mmevent.mmid}_d_YES"), - telegram.InlineKeyboardButton("🔴 Non mi interessa più.", callback_data=f"mm_{mmevent.mmid}_d_NO")] - ]) + [telegram.InlineKeyboardButton("🔵 Ci sarò!", callback_data=f"mm_{mmevent.mmid}_d_YES"), + telegram.InlineKeyboardButton("🔴 Non mi interessa più.", callback_data=f"mm_{mmevent.mmid}_d_NO")] + ]) async def decision_yes(data: CommandData): royal = await data.get_author() mmdecision: MMDecision = await asyncify( - self.interface.session.query(self.interface.alchemy.MMDecision).filter_by(mmevent=mmevent, - royal=royal).one_or_none) + data.session.query(self.interface.alchemy.MMDecision).filter_by(mmevent=mmevent, + royal=royal).one_or_none) if mmdecision is None: mmdecision: MMDecision = self.interface.alchemy.MMDecision(royal=royal, mmevent=mmevent, decision="YES") - self.interface.session.add(mmdecision) + data.session.add(mmdecision) else: mmdecision.decision = "YES" - await asyncify(self.interface.session.commit) + await asyncify(data.session.commit) await update_message() return "🔵 Hai detto che ci sarai!" async def decision_maybe(data: CommandData): royal = await data.get_author() mmdecision: MMDecision = await asyncify( - self.interface.session.query(self.interface.alchemy.MMDecision).filter_by(mmevent=mmevent, - royal=royal).one_or_none) + data.session.query(self.interface.alchemy.MMDecision).filter_by(mmevent=mmevent, + royal=royal).one_or_none) if mmdecision is None: mmdecision: MMDecision = self.interface.alchemy.MMDecision(royal=royal, mmevent=mmevent, decision="MAYBE") - self.interface.session.add(mmdecision) + data.session.add(mmdecision) else: mmdecision.decision = "MAYBE" # Can't asyncify this - self.interface.session.commit() + data.session.commit() await update_message() - return f"⚫️ Hai detto che forse ci sarai. Rispondi al messaggio di conferma {self._cycle_duration} minuti prima dell'inizio!" + return f"⚫️ Hai detto che forse ci sarai." \ + f"Rispondi al messaggio di conferma {self._cycle_duration} minuti prima dell'inizio!" async def decision_no(data: CommandData): royal = await data.get_author() mmdecision: MMDecision = await asyncify( - self.interface.session.query(self.interface.alchemy.MMDecision).filter_by(mmevent=mmevent, - royal=royal).one_or_none) + data.session.query(self.interface.alchemy.MMDecision).filter_by(mmevent=mmevent, + royal=royal).one_or_none) if mmdecision is None: mmdecision: MMDecision = self.interface.alchemy.MMDecision(royal=royal, mmevent=mmevent, decision="NO") - self.interface.session.add(mmdecision) + data.session.add(mmdecision) else: mmdecision.decision = "NO" # Can't asyncify this - self.interface.session.commit() + data.session.commit() await update_message() return "🔴 Hai detto che non ti interessa." @@ -169,43 +169,43 @@ class MmCommand(Command): delay = (datetime.datetime.now() - mmevent.datetime).total_seconds() if delay < 60: return f"🚩 E' ora di [b]{mmevent.title}[/b]!\n" \ - f"Sei pronto?" + f"Sei pronto?" return f"🕒 Sei in ritardo di [b]{int(delay / 60)} minuti[/b] per [b]{mmevent.title}[/b]...\n" \ - f"Sei pronto?" + f"Sei pronto?" response_keyboard = telegram.InlineKeyboardMarkup([ - [telegram.InlineKeyboardButton("✅ Ci sono!", - callback_data=f"mm_{mmevent.mmid}_r_YES")], - [telegram.InlineKeyboardButton("🕒 Aspettatemi ancora un po'!", - callback_data=f"mm_{mmevent.mmid}_r_LATER")], - [telegram.InlineKeyboardButton("❌ Non vengo più, mi spiace.", - callback_data=f"mm_{mmevent.mmid}_r_NO")] - ]) + [telegram.InlineKeyboardButton("✅ Ci sono!", + callback_data=f"mm_{mmevent.mmid}_r_YES")], + [telegram.InlineKeyboardButton("🕒 Aspettatemi ancora un po'!", + callback_data=f"mm_{mmevent.mmid}_r_LATER")], + [telegram.InlineKeyboardButton("❌ Non vengo più, mi spiace.", + callback_data=f"mm_{mmevent.mmid}_r_NO")] + ]) async def response_yes(data: CommandData): royal = await data.get_author() mmresponse: MMResponse = await asyncify( - self.interface.session.query(self.interface.alchemy.MMResponse).filter_by(mmevent=mmevent, - royal=royal).one_or_none) + data.session.query(self.interface.alchemy.MMResponse).filter_by(mmevent=mmevent, + royal=royal).one_or_none) mmresponse.response = "YES" # Can't asyncify this - self.interface.session.commit() + data.session.commit() await update_message() return "✅ Sei pronto!" def later_string(royal) -> str: return f"🕒 {royal.username} ha chiesto di aspettare {self._cycle_duration} prima di iniziare la" \ - f" partita.\n\n" \ - f"Se vuoi iniziare la partita senza aspettarlo, premi Avvia partita su Royal Matchmaking!" + f" partita.\n\n" \ + f"Se vuoi iniziare la partita senza aspettarlo, premi Avvia partita su Royal Matchmaking!" async def response_later(data: CommandData): royal = await data.get_author() mmresponse: MMResponse = await asyncify( - self.interface.session.query(self.interface.alchemy.MMResponse).filter_by(mmevent=mmevent, - royal=royal).one_or_none) + data.session.query(self.interface.alchemy.MMResponse).filter_by(mmevent=mmevent, + royal=royal).one_or_none) mmresponse.response = "LATER" # Can't asyncify this - self.interface.session.commit() + data.session.commit() await self.interface.bot.safe_api_call(client.send_message, chat_id=mmevent.creator.telegram[0].tg_id, text=telegram_escape(later_string(royal)), @@ -217,17 +217,17 @@ class MmCommand(Command): async def response_no(data: CommandData): royal = await data.get_author() mmresponse: MMResponse = await asyncify( - self.interface.session.query(self.interface.alchemy.MMResponse).filter_by(mmevent=mmevent, - royal=royal).one_or_none) + data.session.query(self.interface.alchemy.MMResponse).filter_by(mmevent=mmevent, + royal=royal).one_or_none) mmresponse.response = "NO" # Can't asyncify this - self.interface.session.commit() + data.session.commit() await update_message() return "❌ Hai detto che non ci sarai." def started_string(): text = f"🚩 L'evento [b]{mmevent.title}[/b] è iniziato!\n\n" \ - f"Partecipano:\n" + f"Partecipano:\n" for mmresponse in sorted(mmevent.responses, key=lambda mmr: mmr.response, reverse=True): if mmresponse.response == "YES": text += f"✅ {mmresponse.royal}\n" @@ -236,8 +236,8 @@ class MmCommand(Command): return text started_without_you_string = f"🚩 Non hai confermato la tua presenza in tempo e [b]{mmevent.title}[/b] è" \ - f" iniziato senza di te.\n" \ - f"Mi dispiace!" + f" iniziato senza di te.\n" \ + f"Mi dispiace!" async def start_event(): mmevent.state = "STARTED" @@ -259,7 +259,7 @@ class MmCommand(Command): text=telegram_escape(started_without_you_string), parse_mode="HTML", disable_webpage_preview=True) - await asyncify(self.interface.session.commit) + await asyncify(session.commit) await update_message() async def start_key(data: CommandData): @@ -285,7 +285,7 @@ class MmCommand(Command): parse_mode="HTML", disable_webpage_preview=True, reply_markup=decision_keyboard) - await asyncify(self.interface.session.commit) + await asyncify(session.commit) await update_message() if mmevent.state == "DECISION": @@ -300,8 +300,8 @@ class MmCommand(Command): mmdecision.decision = "NO" elif mmdecision.decision == "YES": mmresponse: MMResponse = self.interface.alchemy.MMResponse(royal=mmdecision.royal, mmevent=mmevent) - self.interface.session.add(mmresponse) - await asyncify(self.interface.session.commit) + session.add(mmresponse) + await asyncify(session.commit) await update_message() if mmevent.state == "READY_CHECK": @@ -347,12 +347,14 @@ class MmCommand(Command): if self.interface.name != "telegram": return log.debug("Loading pending MMEvents from the database") - mmevents = self.interface.session.query(self.interface.alchemy.MMEvent) \ - .filter(self.interface.alchemy.MMEvent.datetime > datetime.datetime.now()) \ - .all() + session = interface.alchemy.Session() + mmevents = session.query(self.interface.alchemy.MMEvent) \ + .filter(self.interface.alchemy.MMEvent.datetime > datetime.datetime.now()) \ + .all() log.info(f"Found {len(mmevents)} pending MMEvents") for mmevent in mmevents: - interface.loop.create_task(self._run_mm(mmevent)) + session = self.interface.alchemy.Session() + interface.loop.create_task(self._run_mm(mmevent, session)) async def run(self, args: CommandArgs, data: CommandData) -> None: if self.interface.name != "telegram": @@ -380,18 +382,19 @@ class MmCommand(Command): title=title, description=description, state="WAITING") - self.interface.session.add(mmevent) - await asyncify(self.interface.session.commit) + data.session.add(mmevent) + await asyncify(data.session.commit) message: telegram.Message = await self.interface.bot.safe_api_call(client.send_message, chat_id=-1001224004974, - text=telegram_escape(self._main_text(mmevent)), + text=telegram_escape( + self._main_text(mmevent)), parse_mode="HTML", disable_webpage_preview=True, reply_markup=self._main_keyboard(mmevent)) mmevent.message_id = message.message_id # Can't asyncify this - await asyncify(self.interface.session.commit) + await asyncify(data.session.commit) - await self._run_mm(mmevent) + await self._run_mm(mmevent, data.session) diff --git a/royalnet/packs/royal/commands/reminder.py b/royalnet/packs/royal/commands/reminder.py index 10991c6d..8626e782 100644 --- a/royalnet/packs/royal/commands/reminder.py +++ b/royalnet/packs/royal/commands/reminder.py @@ -23,13 +23,13 @@ class ReminderCommand(Command): def __init__(self, interface: CommandInterface): super().__init__(interface) + session = interface.alchemy.Session() reminders = ( - interface.session - .query(interface.alchemy.Reminder) - .filter(and_( - interface.alchemy.Reminder.datetime >= datetime.datetime.now(), - interface.alchemy.Reminder.interface_name == interface.name)) - .all() + session.query(interface.alchemy.Reminder) + .filter(and_( + interface.alchemy.Reminder.datetime >= datetime.datetime.now(), + interface.alchemy.Reminder.interface_name == interface.name)) + .all() ) for reminder in reminders: interface.loop.create_task(self._remind(reminder)) @@ -82,5 +82,5 @@ class ReminderCommand(Command): datetime=date, message=reminder_text) self.interface.loop.create_task(self._remind(reminder)) - self.interface.session.add(reminder) - await asyncify(self.interface.session.commit) + data.session.add(reminder) + await asyncify(data.session.commit) diff --git a/royalnet/packs/royal/commands/trivia.py b/royalnet/packs/royal/commands/trivia.py index a3d4c54f..0a82bce5 100644 --- a/royalnet/packs/royal/commands/trivia.py +++ b/royalnet/packs/royal/commands/trivia.py @@ -97,8 +97,8 @@ class TriviaCommand(Command): for answerer in self._answerers[question_id]: if answerer.trivia_score is None: ts = self.interface.alchemy.TriviaScore(royal=answerer) - self.interface.session.add(ts) - self.interface.session.commit() + data.session.add(ts) + data.session.commit() if self._answerers[question_id][answerer]: results += self._correct_emoji answerer.trivia_score.correct_answers += 1 @@ -108,4 +108,4 @@ class TriviaCommand(Command): results += f" {answerer} ({answerer.trivia_score.correct_answers}/{answerer.trivia_score.total_answers})\n" await data.reply(results) del self._answerers[question_id] - await asyncify(self.interface.session.commit) + await asyncify(data.session.commit)