PythonBot - pass argument to command hanlder - python

I'm playing a little bit with Python Telegram Bot, I want to pass to my handler an argument obtained with previous computation, e.g:
def my_handler(bot, update, param):
print(param)
def main():
res = some_function()
updater.dispatcher.add_handler(CommandHandler('cmd', my_handler))
How do I pass param to the handler?

On version 12 of python-telegram-bot, the arguments are located as a list in the attribute CallbackContext.args. Here is a generic example:
def my_handler(update, context):
print(context.args)
def main():
res = some_function()
updater.dispatcher.add_handler(CommandHandler('cmd', my_handler))
A simple example would be summing two integer numbers:
import logging
from config import tgtoken
from telegram.ext import Updater, CommandHandler
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger(__name__)
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, context.error)
def sum(update, context):
try:
number1 = int(context.args[0])
number2 = int(context.args[1])
result = number1+number2
update.message.reply_text('The sum is: '+str(result))
except (IndexError, ValueError):
update.message.reply_text('There are not enough numbers')
def main():
updater = Updater(tgtoken, use_context=True)
dp = updater.dispatcher
dp.add_handler(CommandHandler("sum", sum))
dp.add_error_handler(error)
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()
If you send /sum 1 2, your bot will answer The sum is: 3

If you mean you want to pass to the function called by the handler the argument that the user sends with the command you should add the pass_args=True parameter and it will returns arguments the user sent as a list.
So your code should be:
def my_handler(bot, update, args):
for arg in args:
print(arg)
def main():
res = some_function()
updater.dispatcher.add_handler(CommandHandler('cmd', my_handler, pass_args=True))
I didn't check it tho
If you instead are looking for a way to pass something you took from another handler to that handler related to the same user, that library has a nice parameter called "chat_data" and "user_data".

Related

Pytest patch function used by global variable

I need to test a code that has a global variable which is populated by a function that returns a value after making a connection to external server. For the pytest, I need to figure out a way so that the function does not get called.
I've tried patching both the global variable and the function, but neither worked.
Here's the python code - my_module.py:
import datetime
# %%
def server_function_1 ():
try:
return_val = "Assume the external server function returns a string"
except Exception as e:
print("Failed")
print(e)
raise e
else:
return return_val
finally:
# assume when called from the testing environment, the server connection will raise a exception
raise Exception("Cannot connect to server")
global_result_of_server_func = server_function_1()
# %%
def get_current_datetime_str():
return datetime.datetime.now().strftime('%Y%m%d.%H%M%S.%f')
# %%
def some_function():
return global_result_of_server_func, get_current_datetime_str()
Here's the pytest file - test_my_module.py:
# %%
import pytest
from unittest import mock
import datetime
import logging
import sys
# %%
import my_module
logger = logging.getLogger(__name__)
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
frozen_time = datetime.datetime(year=2022, month=6, day=1, hour=1, minute=0, second=0, microsecond=0)
mock_server_str = "Mock Server String"
# %%
class TestMyModule () :
def test_handler_1 (self):
with mock.patch("my_module.get_current_datetime_str", return_value=frozen_time.strftime('%Y%m%d.%H%M%S.%f')), \
mock.patch("my_module.global_result_of_server_func", new=mock_server_str):
test_server_val, test_frozen_time = my_module.some_function()
assert test_frozen_time == frozen_time.strftime('%Y%m%d.%H%M%S.%f')
assert test_server_val == mock_server_str
def test_handler_2 (self):
with mock.patch("my_module.get_current_datetime_str", return_value=frozen_time.strftime('%Y%m%d.%H%M%S.%f')), \
mock.patch("my_module.server_function_1", return_value=mock_server_str):
test_server_val, test_frozen_time = my_module.some_function()
assert test_frozen_time == frozen_time.strftime('%Y%m%d.%H%M%S.%f')
assert test_server_val == mock_server_str
What I am trying to achieve is that variable global_result_of_server_func gets the mock value, and the function server_function_1 doesn't get called and tries to make a connection to the server.
Thanks.
Delaying the import like the suggestion in this question didn't seem to make any difference.

Cannot trigger an async function from another threaded function in Python

I am making a discord bot that will grab a json using requests from time to time, and then send the relevant information to a specific channel.
I have the following classes:
Helper, which is the discord bot itself, that runs async from the start, inside an asyncio.gather;
tasker that controls the interval which calls the class that will do the requests. It runs in a different thread so it doesn't stop the async Helper while it waits
getInfo that does the requests, store the info and should talk with Helper
I am having 2 problems right now:
While the tasker is on a different thread, every time I try to talk with Helper via getInfo it gives me the errors RuntimeError: no running event loop and RuntimeWarning: coroutine 'getInfo.discordmsg' was never awaited
If I dont run it on a different thread, however, it does work on the TestStatus: 1 but it makes Helper get stuck and stop running with TestStatus: 2
Anyway, here is the code
import requests
import asyncio
import discord
from discord.ext import commands, tasks
from datetime import datetime, timedelta
import threading
class Helper(discord.Client):
async def on_ready(self):
global discordbot, taskervar
servername = 'ServerName'
discordbot = self
self.servidores = dict()
self.canais = dict()
for i in range(len(self.guilds)):
self.servidores[self.guilds[i].name] = {}
self.servidores[self.guilds[i].name]['guild']=self.guilds[i]
servidor = self.guilds[i]
for k in range(len(servidor.channels)):
canal = servidor.channels[k]
self.canais[str(canal.name)] = canal
if 'bottalk' not in self.canais.keys():
newchan = await self.servidores[self.guilds[i].name]['guild'].create_text_channel('bottalk')
self.canais[str(newchan.name)] = newchan
self.servidores[self.guilds[i].name]['canais'] = self.canais
self.bottalk = self.get_channel(self.servidores[servername]['canais']['bottalk'].id)
await self.msg("Bot online: " + converteHora(datetime.now(),True))
print(f'{self.user} has connected to Discord!')
taskervar.startprocess()
async def msg(self, msg):
await self.bottalk.send(msg)
async def on_message(self, message):
if message.author == self.user:
return
else:
print(message)
class tasker:
def __init__(self):
global discordbot, taskervar
print('Tasker start')
taskervar = self
self.waiter = threading.Event()
self.lastupdate = datetime.now()
self.nextupdate = datetime.now()
self.thread = threading.Thread(target=self.requests)
def startprocess(self):
if not self.thread.is_alive():
self.waiter = threading.Event()
self.interval = 60*5
self.thread = threading.Thread(target=self.requests)
self.thread.start()
def requests(self):
while not self.waiter.is_set():
getInfo()
self.lastupdate = datetime.now()
self.nextupdate = datetime.now()+timedelta(seconds=self.interval)
self.waiter.wait(self.interval)
def stopprocess(self):
self.waiter.set()
class getInfo:
def __init__(self):
global discordbot, taskervar
self.requests()
async def discordmsg(self,msg):
await discordbot.msg(msg)
def requests(self):
jsondata = {"TestStatus": 1}
if jsondata['TestStatus'] == 1:
print('here')
asyncio.create_task(self.discordmsg("SOMETHING WENT WRONG"))
taskervar.stopprocess()
return
elif jsondata['TestStatus'] == 2:
print('test')
hora = converteHora(datetime.now(),True)
asyncio.create_task(self.discordmsg(str("Everything is fine but not now: " + hora )))
print('test2')
def converteHora(dateUTC, current=False):
if current:
response = (dateUTC.strftime("%d/%m/%Y, %H:%M:%S"))
else:
response = (dateutil.parser.isoparse(dateUTC)-timedelta(hours=3)).strftime("%d/%m/%Y, %H:%M:%S")
return response
async def main():
TOKEN = 'TOKEN GOES HERE'
tasker()
await asyncio.gather(
await Helper().start(TOKEN)
)
if __name__ == '__main__':
asyncio.run(main())
Your primary problem is you don't give your secondary thread access to the asyncio event loop. You can't just await and/or create_task a coroutine on a global object (One of many reasons to avoid using global objects in the first place). Here is how you could modify your code to accomplish that:
class tasker:
def __init__(self):
# ...
self.loop = asyncio.get_running_loop()
# ...
class getInfo:
#...
def requests(self):
# replace the create_tasks calls with this.
asyncio.run_coroutine_threadsafe(self.discordmsg, taskervar.loop)
This uses your global variables because I don't want to rewrite your entire program, but I still strongly recommend avoiding them and considering a re-write yourself.
All that being said, I suspect you will still have this bug:
If I dont run it on a different thread, however, it does work on the TestStatus: 1 but it makes Helper get stuck and stop running with TestStatus: 2
I can't tell what would cause this issue and I'm running into trouble reproducing this on my machine. Your code is pretty hard to read and is missing some details for reproducibility. I would imagine that is part of the reason why you didn't get an answer in the first place. I'm sure you're aware of this article but might be worth a re-visit for better practices in sharing code. https://stackoverflow.com/help/minimal-reproducible-example

How to update time variable string in python-telegram-bot

I've created echo chat telegram bot with python-telegram-bot. It'll echo everything that I typed in with time. But the problem is It always echoes same time string since the bot start.
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, Dispatcher
import logging
import datetime
logging.basicConfig(format='%(levelname)s - %(message)s',
level=logging.DEBUG)
logger = logging.getLogger(__name__)
updater = None
t = datetime.now()
dt_string = time.strftime("%b/%d/%Y %H:%M:%S", t)
sitepath=""
filename="output.txt"
def updatetime():
t = datetime.now()
dt_string = time.strftime("%b/%d/%Y %H:%M:%S", t)
def repeater(update, context):
updatetime()
update.message.reply_text("Done: " + update.message.text + "\n"+ dt_string)
def start_bot():
global updater
updater = Updater(
'##Token##', use_context=True)
dispatcher = updater.dispatcher
dispatcher.add_handler(MessageHandler(Filters.text, repeater))
updater.start_polling()
updater.idle()
start_bot()
Expected result is
Done: This is a message
Feb/05/2021 15:13:34
Done: 10 Second have passed
Feb/05/2021 15:13:44
Done: 10 Second have passed
Feb/05/2021 15:13:54
But this is the actual result
Done: This is a message
Feb/05/2021 15:13:34
Done: 10 Second have passed
Feb/05/2021 15:13:34
Done: 10 Second have passed
Feb/05/2021 15:13:34
You need to add global keyword in the methods to make sure you use the global variable dt_string else it will create a local variable and you would not be updating the global variable.
def updatetime():
global dt_string
t = datetime.now()
dt_string = time.strftime("%b/%d/%Y %H:%M:%S", t)
You need to do the same for all methods and all variables.
Note that the use of global is not recommended so you should try to refactor your code to avoid use of global variables.
The solution by Krishna Chaurasia is right about everything: the use of global variables and the not recommended use of global variables.
A simpler solution is to remove def updatetime() entirely and insert the dt_string on repeater function, like this:
def repeater(update, context):
## dt_string will update everytime you call repeater
## datetime.now() it's not necessary
dt_string = time.strftime("%b/%d/%Y %H:%M:%S")
update.message.reply_text("Done: " + update.message.text + "\n"+ dt_string)
This will make you code short and easier to read.

Stopping log script from being accessed by multiple threads

I have a module called myLog.py which is being accessed by multiple other modules in a project. The myLog.py module has two handlers: file_handler that inputs logs into file and stream_handler that outputs logs to a console. For modules where no threading is occurring i.e myLog.py is only being accessed by a single process the logs are being inserted properly but for modules where threading is being implemented i.e myLog.py is being accessed by multiple processes at the same time I am getting multiple logs of the same line being inserted in my log_file.txt.
While going through logging documentation I found out that logging module is thread_safe but my implementation says things differently. How should I initialize the function setLogger() in myLog.py such that if it gets accessed by multiple threads at the same time it gives the correct output?
#myLog.py
#setup of logger
def setLogger(logfile_name = "log_file.txt"):
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(message)s')
file_handler = logging.FileHandler(logfile_name)
file_handler.setFormatter(formatter)
stream_handler = logging.StreamHandler()
logger.addHandler(file_handler)
logger.addHandler(stream_handler)
return logger
So suppose for example it is being accessed by a module called parser.py which implements threading then the log statements get printed out in a very random duplicated fashion.
#parser.py
import threading
import myLog
logger = myLog.setLogger()
class investigate(threading.Thread):
def __init__(self, section, file, buffer, args):
threading.Thread.__init__(self)
self.section = section
self.file = file
self.buffer = buffer
self.args = args
self.sig = self.pub = None
self.exc = None
def run(self):
aprint("Starting section %d file %d" % (self.section, self.file))
self.exc = None
try:
self.sign()
aprint("Done section %d file %d" % (self.section, self.file))
except:
self.exc = sys.exc_info()
def sign(self):
self.sig, self.pub = sign_hsm(self.buffer, self.args)
if self.sig is None or self.pub is None:
raise Exception("Empty signing result")
def store(self, bot):
sec = filter(lambda x: x.type == self.section, bot.sections)[0]
if self.file == 0xFF:
signature = sec.signature
else:
signature = sec.files[self.file].signature
signature.sig = self.sig
signature.pub = self.pub
def join(self, *args, **kwargs):
threading.Thread.join(self, *args, **kwargs)
if self.exc:
msg = "Thread '%s' threw an exception: %s" % (self.getName(), self.exc[1])
new_exc = Exception(msg)
raise new_exc.__class__, new_exc, self.exc[2]
def PrintVersion():
logger.info("This is output.")
print_lock = threading.RLock()
def aprint(*args, **kwargs):
if verbosityLevel > 0:
with print_lock:
return logger.info(*args, **kwargs)
def multipleTimes():
logger.info("Multiple times.")
if __name__ == "__main__":
PrintVersion()
for investigate in investigations:
investigate.start()
.......
.......
.......
logger.info("This gets repeated")
multipleTimes()
So since multiple threads are trying to access the setLogger() I get logger.info() outputs such as:
This is output.
This is output.
This is output.
This is output.
This is output.
This gets repeated.
This gets repeated.
This gets repeated.
Multiple times.
Multiple times.
Multiple times.
Multiple times.
Multiple times.
Multiple times.
What I should be getting:
This is output.
This gets repeated.
Multiple times.

Thread-safe warnings in Python

I am trying to find a good way to log a warning message but appending to it information that is only known by the caller of the function.
I think it will be clear with an example.
# log method as parameter
class Runner1(object):
def __init__(self, log):
self.log = log
def run(self):
self.log('First Warning')
self.log('Second Warning')
return 42
class Main1(object):
def __init__(self):
self._runner = Runner1(self.log)
def log(self, message):
print('Some object specific info: {}'.format(message))
def run(self):
print(self._runner.run())
e1 = Main1()
e1.run()
The Main object has a log function that adds to any message its own information before logging it. This log function is given as a parameter (in this case to a Runner object). Carrying this extra parameter all the time is extremely annoying and I would like to avoid it. There are usually lots of object/functions and therefore I have discarded the use of the logging method as I would need to create a different logger for each object. (Is this correct?)
I have tried to bubble the warning using the warning module:
# warning module
import warnings
class Runner2(object):
def run(self):
warnings.warn('First Warning')
warnings.warn('Second Warning')
return 42
class Main2(object):
def __init__(self):
self._runner = Runner2()
def log(self, message):
print('Some object specific info: {}'.format(message))
def run(self):
with warnings.catch_warnings(record=True) as ws:
warnings.simplefilter("always")
out = self._runner.run()
for w in ws:
self.log(w.message)
print(out)
e2 = Main2()
e2.run()
But according to the docs, this is not thread safe.
Finally, I have also tried some generators:
# yield warning
class _Warning(object):
def __init__(self, message):
self.message = message
class Runner3(object):
def run(self):
yield _Warning('First Warning')
yield _Warning('Second Warning')
yield 42
class Main3(object):
def __init__(self):
self._runner = Runner3()
def log(self, message):
print('Some object specific info: {}'.format(message))
def run(self):
for out in self._runner.run():
if not isinstance(out, _Warning):
break
self.log(out.message)
print(out)
e3 = Main3()
e3.run()
But the fact that you have to modify the Runner.run to yield (instead of return) the final result is inconvenient as functions will have to be specifically changed to be used in this way (Maybe this will change in the future? Last QA in PEP255). Additionally, I am not sure if there is any other trouble with this type of implementation.
So what I am looking for is a thread-safe way of bubbling warnings that does not require passing parameters. I also would like that methods that do not have warnings remain unchanged. Adding a special construct such as yield or warning.warn to bubble the warnings would be fine.
Any ideas?
import Queue
log = Queue.Queue()
class Runner1(object):
def run(self):
log.put('First Warning')
log.put('Second Warning')
return 42
class Main1(object):
def __init__(self):
self._runner = Runner1()
def log(self, message):
print('Some object specific info: {0}'.format(message))
def run(self):
out=self._runner.run()
while True:
try:
msg = log.get_nowait()
self.log(msg)
except Queue.Empty:
break
print(out)
e1 = Main1()
e1.run()
yields
Some object specific info: First Warning
Some object specific info: Second Warning
42

Categories

Resources