Python Flask with Telethon - python

I want to use Telethon Telegram API from my Flask Web App. But when I am running it, I am getting following error:
RuntimeError: There is no current event loop in thread 'Thread-1'.
I think there is some issues with asyncio. But I am not sure about that.
Here is my code
#!/usr/bin/python3
from flask import Flask
from telethon import TelegramClient
from telethon import sync
app = Flask(__name__)
#app.route('/')
def index():
api_id = XXXXXX
api_hash = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
client = TelegramClient('XXXXXX', api_id, api_hash)
client.start()
return 'Index Page'
if __name__ == '__main__':
app.run()

Here's what I learned after trying this out. First, Make sure you know what asyncio is, it's really super easy. Then You can work on it with more productivity.
Telethon uses asyncio which means that when you call blocking methods you have to wait until the coroutine finishes.
client.loop ###Doesn't work inside flask, it might have to do with threads.
You can easily import asyncio and use the main loop. like this.
import asyncio
loop = asyncio.get_event_loop()
Now you're ready to wait for coroutines to finish.
Create a new async function and add await to the blocking methods.
Execute the code using the main event loop.
Here's a code sample.
async def getYou():
return await client.get_me()
#app.route("/getMe", methods=['GET'])
def getMe():
return {"MyTelegramAccount": loop.run_until_complete(getYou())}
And one more thing. don't use telethon.sync, it's not fully translated to sync, it uses the above pattern it awaits all of the methods.

Basically, it's due to Python's GIL. If you don't want to dig into asyncio internals, just pip3 install telethon-sync and you're good to go.

In your place I would consider using Quart it's suggested on Telethon own's documentation and it's much easier.

Related

Python flask socketio with discord.py

I am trying to have a server that will be able to give us the status on discord.
I did achieve this by having an extra socketio client that run discord.py aswell.
I would like discord to run on the server instead of on a client. I am unable to make this work at the same time, any insight or help would be appreciated.
I tried launching discord before socketio, also tried launching discord in a different thread.
--Update--
The orginal post had a emit onConenct. this is apparently a known issue and will cause a namespace error. . please find trhe edited code below
as a minimal reproductable example here is the code:
server:
import asyncio
from time import sleep
from flask import Flask
from aiohttp import web
import socketio
from discord.ext import commands
sio = socketio.AsyncServer(async_mode='aiohttp',logger=True)
app = web.Application()
sio.attach(app)
bot = commands.Bot(command_prefix='+')
#bot.command(help="testing")
async def test(ctx):
await ctx.send("Hello world!")
await sio.emit('marco')
#sio.on('polo')
async def message(sid):
print("a wild client responded")
async def start_discord():
print('starting discord')
await bot.start('token')
if __name__ == '__main__':
asyncio.run(asyncio.gather(web.run_app(app),start_discord()))
client :
import asyncio
from click import prompt
import socketio
from discord.ext import commands
sio = socketio.Client(logger=True)
#sio.on("marco")
def polo():
sio.emit('polo')
print("wow i found a server")
if __name__ == "__main__":
sio.connect("http://localhost:8080")
sio.wait()
Ther server and client are connecting but not discord.
Your issue is that the socket server only starts when the discord bot exists. This will never happen.
If you want to run 2 functions concurrently use asyncio.gather in combination with client.start
Like this:
if __name__ == '__main__':
asyncio.run(
asyncio.gather(
bot.start('SuperSecret Token')
sio.start(app, host="localhost", port=8080)
)
)
This will ensure they start the the same time.
Note your code to create a custom event loop is probably unnecessary.
Having multiple calls to asyncio.run in your code is a mistake 99% of the time. You normally need only one async context.

Using two PIP packages together that use event loops

I'm trying to combine a Twitch API package (twitchio) with a webserver (sanic) with the intent of serving chat commands to a game running locally to the python script. I don't have to use Sanic or twitchio but those are the best results I've found for my project.
I think I understand why what I have so far isn't working but I'm at a total loss of how to resolve the problem. Most of the answers I've found so far deal with scripts you've written to use asyncio and not packages that make use of event loops.
I can only get the webserver OR the chat bot to function. I can't get them to both run concurrently.
This is my first attempt at using Python so any guidance is greatly appreciated.
#!/usr/bin/python
import os
from twitchio.ext import commands
from sanic import Sanic
from sanic.response import json
app = Sanic(name='localserver')
bot = commands.Bot(
token=os.environ['TMI_TOKEN'],
client_id=os.environ['CLIENT_ID'],
nick=os.environ['BOT_NICK'],
prefix=os.environ['BOT_PREFIX'],
initial_channels=[os.environ['CHANNEL']]
)
#app.route("/")
async def query(request):
return json(comcache)
#bot.event
async def event_ready():
'Called once when the bot goes online.'
print(f"{os.environ['BOT_NICK']} is online!")
ws = bot._ws # this is only needed to send messages within event_ready
await ws.send_privmsg(os.environ['CHANNEL'], f"/me has landed!")
#bot.event
async def event_message(ctx):
'Runs every time a message is sent in chat.'
# make sure the bot ignores itself and the streamer
if ctx.author.name.lower() == os.environ['BOT_NICK'].lower():
return
await bot.handle_commands(ctx)
# await ctx.channel.send(ctx.content)
if 'hello' in ctx.content.lower():
await ctx.channel.send(f"Hi, #{ctx.author.name}!")
#bot.command(name='test')
async def test(ctx):
await ctx.send('test passed!')
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8080)
bot.run()

Running Discord.py client & Uvicorn app simultaneously

I've been trying to implement a Discord.py bot with a basic FastAPI application.
The API runs with a uvicorn.Server class from uvicorn#742, with a run_in_thread() function, so both processes can't really hold each other.
Unfortunately, when I run both apps together.. it seems that the Discord client is preventing the API from receiving web requests, I believe that's because the bot client uses a bunch of network frameworks. I've tried multiple approaches in solving this issue, but just can't seem to find a right solution.
The root directory contains 2 files.. api.py just for the FastAPI app, and main.py for everything else (client, classes, etc). But I will try to just provide the important parts.
# main.py
from discord.ext.commands import Bot
import uvicorn, contextlib, time
client = Bot() # just a basic discord.ext.commands.Bot class, nothing fancy here.
#client.event
async def on_ready(self):
print(f"Bot online, logged in as {client.user}")
# https://github.com/encode/uvicorn/issues/742#issuecomment-674411676
class Webhook(uvicorn.Server):
def install_signal_handlers(self): pass
#contextlib .contextmanager
def run_in_thread(self):
thread = Thread(target = self.run)
thread.start()
try:
while not self.started:
time.sleep(1e-3)
yield
finally:
self.should_exit = True
thread.join()
if __name__ == "__main__":
api_server = Webhook(
config = uvicorn.Config("api:app", # you can guess what api.py contains already.
host = "127.0.0.1",
port = 8000,
log_level = "info",
loop = "asyncio",
workers = 3)
)
with api_server.run_in_thread():
client.run(TOKEN)
I've played with this a lot, either the bot or the API works in each method. My best guess is that it has to do with event loops, uvicorn.Server loop seems to be hard to handle, but I'm only intermediate with that.

Get realtime updates with Cloud Firestore with asyncio

How can I run this example through asyncio? The error I got on startup:
name col_snapshot is not defined
sample:
async def on_snapshot(col_snapshot, changes, read_time):
logger.debug('Received updates')
col_query = db.collection(u'cities')
query_watch = col_query.on_snapshot(await on_snapshot(col_snapshot, changes, read_time))
Update: Async callbacks example
import os
import asyncio
from loguru import logger
from google.cloud.firestore import Client
os.environ['GOOGLE_APPLICATION_CREDENTIALS']='E:/Python/listener_firebase/creds.json'
async def callback(col_snapshot, changes, read_time):
logger.debug('Received updates')
for change in changes:
if change.type.name == 'ADDED':
logger.debug(f'New document: {change.document.id}')
await asyncio.sleep(1)
logger.debug('Finished handling the updates')
Client().collection('cities').on_snapshot(callback)
while True:
pass
This doesn't work for me:
tch.py:568: RuntimeWarning: coroutine 'callback' was never awaited
self._snapshot_callback(keys, appliedChanges, read_time)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
As it's written, your code shouldn't use asyncio at all. If, however, await asyncio.sleep(1) is there to simulate some I/O heavy code that takes advantage of asyncio, your options here aren't great. The first solution would be to use an asyncio-aware client for Firestore. I have no idea if one exists. Assuming one doesn't, and, as you say, you need to use an event loop that is already running, you need to define a synchronous function (since that's what needs to be passed to the client's on_snapshot) that schedules your asynchronous callback with the event loop.
import os
import asyncio
from loguru import logger
from google.cloud.firestore import Client
os.environ['GOOGLE_APPLICATION_CREDENTIALS']='E:/Python/listener_firebase/creds.json'
async def asynchronous_callback(changes):
logger.debug('Received updates')
for change in changes:
if change.type.name == 'ADDED':
logger.debug(f'New document: {change.document.id}')
await asyncio.sleep(1)
logger.debug('Finished handling the updates')
def synchronous_callback(col_snapshot, changes, read_time):
asyncio.create_task(asynchronous_callback(changes))
Client().collection('cities').on_snapshot(synchronous_callback)
while True:
pass
Really, though, your best option is not to use asyncio for your callback since the Firestore client is already running your code synchronously.

Gunicorn gevent: There is no current event loop in thread

I have an application using async io to perform parallel requests to different services.
It's a flask APP running with Gunicorn.
Unfortunately, some of the requests are kind of long (up to 10s). Until now, I was using the base worker from Gunicorn (sync), but since they are a finited number, I sometime run out of them.
So I've heard about the gevent worker class which for most of the requests allows me to process in parallel, but I don't get how I'm supposed to deal with the code using asyncio. I've reproduced my issue with this simple example :
I use this command to start the server :
gunicorn test_wsgi:app --config=test_wsgi_config.py
With test_wsgi.py:
import asyncio
from flask import Flask
app = Flask(__name__)
async def a_long_task():
await asyncio.sleep(5)
#app.route('/')
def hello():
loop = asyncio.get_event_loop()
loop.run_until_complete(
loop.create_task(a_long_task())
)
return f'Hello, world'
And test_wsgi_config.py
worker_class = "gevent"
When I use worker_class = sync, it works fine, but all the requests are queued. But with gevent, I keep having :
RuntimeError: There is no current event loop in thread 'DummyThread-1'
If I create an event loop :
#app.route('/')
def hello():
asyncio.set_event_loop(asyncio.new_event_loop())
loop = asyncio.get_event_loop()
loop.run_until_complete(
loop.create_task(a_long_task())
)
return f'Hello, world'
I get :
RuntimeError: This event loop is already running
When I do several commands likes this one :
curl 127.0.0.1:8000 &
I'm not sure of how I'm supposed to deal with that.
I managed to get your example working by making a small change to how you get your event loop:
import asyncio
from flask import Flask
app = Flask(__name__)
async def a_long_task():
await asyncio.sleep(5)
#app.route('/')
def hello():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(
loop.create_task(a_long_task())
)
return f'Hello, world'
This works because we get a new event loop, which should always work, then set it as the current one.

Categories

Resources