quart is not able to process multi request from a background task - python

We are using quart from last 6 months and it was a awesome journey with it. Now we have a new requirement where we need to build a request engine using which we can make request to any url. Now we have tried different url and it work perfectly whereas it fails on the same server request. As it was already running for 3-4 months we can not make a full refactor. is there a better way to handle this scenario?
from quart import Quart, request
import time
import asyncio
import requests
app = Quart(__name__)
#app.route("/", methods=["POST", "GET"])
async def main_route():
print("Hello from main route")
await asyncio.sleep(1)
loop = asyncio.get_event_loop()
async def handle_future(timeout):
print("future is here")
res = requests.get('http://0.0.0.0:5000/test')
print("Response from test route",res.text)
await asyncio.sleep(timeout)
print("I am completed")
def call_soon():
asyncio.ensure_future(handle_future(10))
loop.call_soon_threadsafe(call_soon)
return "Hello from root"
#app.route("/test", methods=["POST", "GET"])
async def test_route():
print("Hello from test route")
await asyncio.sleep(0.5)
return "Hello from test" + str(time.time())
if __name__ == '__main__':
from hypercorn.config import Config
from hypercorn.asyncio import serve
Config.bind = ["0.0.0.0:5000"]
asyncio.run(serve(app, Config()))
Output
* Serving Quart app 'index'
* Environment: production
* Please use an ASGI server (e.g. Hypercorn) directly in production
* Debug mode: False
* Running on http://0.0.0.0:5000 (CTRL + C to quit)
[2022-03-22 10:54:09,047] Running on http://0.0.0.0:5000 (CTRL + C to quit)
Hello from main route
[2022-03-22 10:54:13,775] 127.0.0.1:49650 GET / 1.1 200 15 1009876
future is here
It was hanged infinitely until the request is timed out. And moreover, it is not accepting any more request during this period

I'd recommend you use a background task to run a coroutine function in the background (via add_background_task). I'd also recommend you switch from requests to httpx as httpx makes requests without blocking the event loop (you can also use requests by using the app.ensure_async function). This is what I suggest,
import asyncio
import time
import httpx
from quart import Quart, request
app = Quart(__name__)
async def _background_task(timeout):
async with httpx.AsyncClient() as client:
response = await client.get('http://0.0.0.0:5000/test')
print("Response from test route", response.text)
await asyncio.sleep(timeout)
print("I am completed")
#app.route("/", methods=["POST", "GET"])
async def main_route():
print("Hello from main route")
await asyncio.sleep(1)
app.add_background_task(_background_task, 10)
return "Hello from root"
#app.route("/test", methods=["POST", "GET"])
async def test_route():
print("Hello from test route")
await asyncio.sleep(0.5)
return "Hello from test" + str(time.time())
if __name__ == '__main__':
from hypercorn.config import Config
from hypercorn.asyncio import serve
Config.bind = ["0.0.0.0:5000"]
asyncio.run(serve(app, Config()))

Related

Scheduled HTTP Request using FastAPI

Inside my FastAPI application, I would like to schedule an HTTP request to be made to check for new results (comparing to database) every X time interval. What would be the easiest way to accomplish this using httpx?
You can add an async task to the event loop during the startup event. This async task would check (and sleep) and store the result somewhere. In the below example, I've chosen to pass around a shared object using the app.state feature of FastAPI. This should give you enough pointers to implement your exact use case. I have commented out an example of dealing with https specifically.
from fastapi import FastAPI
import asyncio
class MySharedObject:
def __init__(self) -> None:
self.count = 0
async def timed_checker(obj: MySharedObject):
while True:
obj.count += 1
# async with httpx.AsyncClient() as client:
# r = await client.get('https://www.example.com/')
await asyncio.sleep(3)
app = FastAPI()
#app.on_event("startup")
def startup_function():
app.state.shared_object = MySharedObject()
asyncio.create_task(timed_checker(app.state.shared_object))
#app.get("/")
async def root():
return {"hello": "world"}
#app.get("/count")
async def get_count():
return app.state.shared_object.count
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)

Asyncio run Dash (Flask) server with another coroutine concurrently

I created a dash app to present information that another code is collecting, I want to run them both concurrently using the asyncio module in Python.
My code is using async functions and the Dash app (which is based on Flask) is blocking anything else from executing while serving.
I'm not sure if this is something that has to involve opening up more threads.
Here's my current code which only runs the main coroutine.
async def main():
some code here...
while True:
try:
await client.handle_message()
except ConnectionClosedError as error:
logger.error(error)
for strategy in strategies:
await asyncio.create_task(...)
some code here...
async def run_dashboard():
app = create_app()
app.run_server('0.0.0.0', 5000, debug=False)
if __name__ == '__main__':
some code here...
# Currently just runs the main coroutine
asyncio.run(main())
How can I run main and run_dashboard concurrently?
Frankly speaking it is not good design to combine Dash (Flask) with some async work in one process, consider to run Flask and async activities in different processes (i.e. apps).
Nevertheless, If you still want to run all in one process, I can give you the following working example, please follow comments and ask if you have any questions:
from flask import Flask, jsonify
import asyncio
from threading import Thread
# ** Async Part **
async def some_print_task():
"""Some async function"""
while True:
await asyncio.sleep(2)
print("Some Task")
async def another_task():
"""Another async function"""
while True:
await asyncio.sleep(3)
print("Another Task")
async def async_main():
"""Main async function"""
await asyncio.gather(some_print_task(), another_task())
def async_main_wrapper():
"""Not async Wrapper around async_main to run it as target function of Thread"""
asyncio.run(async_main())
# *** Flask Part ***:
app = Flask(__name__)
#app.route("/", methods=["GET"])
def index():
"""just some function"""
return jsonify({"hello": "world"})
if __name__ == '__main__':
# run all async stuff in another thread
th = Thread(target=async_main_wrapper)
th.start()
# run Flask server
app.run(host="0.0.0.0", port=9999)
th.join()
Here is some code running a dash app (collecting flight data - courtesy Jose Portilla - Udemy) + the threading to run the dash app and some tasks in async.
from flask import Flask, jsonify
import asyncio
from threading import Thread
# Dash
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import requests
import plotly.graph_objects as go
# ** Async Part **
async def some_print_task():
"""Some async function"""
while True:
await asyncio.sleep(2)
print("Some Task")
async def another_task():
"""Another async function"""
while True:
await asyncio.sleep(3)
print("Another Task")
async def async_main():
"""Main async function"""
await asyncio.gather(some_print_task(), another_task())
def async_main_wrapper():
"""Not async Wrapper around async_main to run it as target function of Thread"""
asyncio.run(async_main())
# *** Dash Part ***:
app = dash.Dash()
app.layout = html.Div([
# html.Div([
# html.Iframe(src="https://www.flightradar24.com",
# height=500,width=200)
# ]),
html.Div([
html.Pre(id='counter-text',children='Active Flights Worldwide'),
dcc.Graph(id='live-update-graph',style={'width':1200}),
dcc.Interval( id='interval-component',
interval=6000,
n_intervals=0)
])
])
counter_list = []
#app.callback( Output('counter-text','children'),
[Input('interval-component','n_intervals')])
def update_layout(n):
url = "https://data-live.flightradar24.com/zones/fcgi/feed.js?faa=1&mlat=1&flarm=1&adsb=1&gnd=1&air=1&vehicles=1&estimated=1&stats=1"
res = requests.get(url, headers={'User-Agent' : 'Mozilla/5.0'}) # A fake header is necessary to access the site
data = res.json()
counter = 0
for element in data["stats"]["total"]:
counter += data["stats"]["total"][element]
counter_list.append(counter)
return "Active flights Worldwide: {}".format(counter)
#app.callback( Output('live-update-graph','figure'),
[Input('interval-component','n_intervals')])
def update_graph(n):
fig = go.Figure(data=[
go.Scatter(x=list(range(len(counter_list))),
y=counter_list,
mode='lines+markers')
])
return fig
if __name__ == '__main__':
# run all async stuff in another thread
th = Thread(target=async_main_wrapper)
th.start()
# run Flask server
# app.run(host="0.0.0.0", port=9999)
app.run_server(debug=True)
th.join()
If you really want it to run all in one process, the following worked for me:
from functools import partial
from threading import Thread
partial_run = partial(app.run, host="0.0.0.0", port=5000, debug=True, use_reloader=False)
t = Thread(target=partial_run)
t.start()
asyncio.run(main())
Run run_dashboard in a background thread. Ref to the document.
async def run():
await asyncio.gather(
asyncio.to_thread(run_dashboard),
main()
)
asyncio.run(run())
Note that asyncio.to_thread is a new function in version 3.9.
For python version older than 3.9, copy the following code
threads.py.

Expose discord bot to API (Flask, FASTAPI)

I'm building a discord bot to take commands from multiple systems and programs. I'm wanting to expose certain actions of my discord bot to REST endpoints and then execute said actions in one spot.
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
from discord.ext import commands
app = FastAPI()
TOKEN = 'MY_TOKEN'
bot = commands.Bot(command_prefix='>')
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
#app.get("/")
def hello():
return {"message":"Hello"}
#app.post("/items/")
async def create_item(item: Item):
await send_message()
return item
#bot.event
async def on_ready():
print(f'{bot.user.name} has connected to Discord!')
async def send_message():
user = await bot.fetch_user(USER_ID)
await user.send('👀')
if __name__ == "__main__":
bot.run('BOT_TOKEN')
uvicorn.run(app, host='0.0.0.0')
When I try to run this, I'm only seeing the bot active. I'm a little newer to python but a veteran programmer. Is this due to python's "lack" of multithreading? Or port usage?
The end goal is to call the "/items/" endpoint and see a message on discord sent to me
EDIT
I tried all the answers and coming up with some of my own. The problem is multi-threading. I got frustrated with it and ended up just moving this piece to Node.js. It doesn't technically fulfill this question but was far easier than navigating python multithreading.
server.js:
var express = require('express');
var app = express();
const Discord = require('discord.js');
const client = new Discord.Client();
app.get('/listUsers', function (req, res) {
dm_user();
res.send('hello');
})
client.on('ready', () => {
console.log(`Logged in as ${client.user.tag}!`);
});
client.on('message', msg => {
if (msg.content === 'ping') {
msg.reply('pong');
}
});
async function dm_user(id){
var my_user = await client.users.fetch('USER_ID');
console.log(my_user);
}
var server = app.listen(8081, function () {
var host = server.address().address
var port = server.address().port
console.log("Example app listening at http://%s:%s", host, port)
client.login('TOKEN');
})
According to the discord.py docs bot.run() is "A blocking call that abstracts away the event loop initialisation from you." and further they said if we want more control over the loop we could use start() coroutine instead of run(). So now we should create a task for calling this coroutine and we know discord.py and FastAPI all are asynchronous applications. For starting a FastAPI app you need an ASGI server to handle it. In this case, we're using Uvicorn. So far we have run FastAPI app, now we need to start our discord bot. According to FastAPI docs we could use startup/shutdown event, for calling bot.start() coroutine before the main API starts.
Here is an example of an app which has an API endpoint for sending a message to a discord's user:
import asyncio
import discord
import uvicorn
from config import TOKEN, USER_ID
from fastapi import FastAPI
app = FastAPI()
bot = discord.Client()
#app.on_event("startup")
async def startup_event(): #this fucntion will run before the main API starts
asyncio.create_task(bot.start(TOKEN))
await asyncio.sleep(4) #optional sleep for established connection with discord
print(f"{bot.user} has connected to Discord!")
#app.get("/")
async def root(msg: str): #API endpoint for sending a message to a discord's user
user = await send_message(msg)
return {"Message": f"'{msg}' sent to {user}"}
async def send_message(message):
user = await bot.fetch_user(USER_ID)
await user.send(message)
return user #for optional log in the response of endpoint
if __name__ == "__main__":
uvicorn.run(app, host="localhost", port=5000)
Tested with Python 3.7.4
You are not returning anything from your send_message function. Something like this should do good.
#app.post("/items/")
async def create_item(item: Item):
msg = await send_message()
return msg
async def send_message():
user = await bot.fetch_user(USER_ID)
return await user.send('👀')
Code bot.run(...) runs all time and it blocks next line which starts API. You would have to run one of them in separated thread or process.
I tried to run bot in thread
if __name__ == "__main__":
import threading
print('Starting bot')
t = threading.Thread(target=bot.start, args=(TOKEN,))
t.start()
print('Starting API')
uvicorn.run(app, host='0.0.0.0')
but it gives me message that bot should run in main thread.
But I found question Discord bot and bottle in the same time in Python and base on it I create code which works for me
if __name__ == "__main__":
import asyncio
print('Starting bot')
bot_app = bot.start(TOKEN)
bot_task = asyncio.ensure_future(bot_app)
print('Starting API')
uvicorn.run(app, host='0.0.0.0')
But I'm not sure if this is ellegant method because uvicorn runs ayncio indirectly.
Full version
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
from discord.ext import commands
app = FastAPI()
#import os
#TOKEN = os.getenv("DISCORD_TOKEN")
TOKEN = 'MY_TOKEN'
bot = commands.Bot(command_prefix='>')
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
#app.get("/")
def hello():
return {"message":"Hello"}
#app.post("/items/")
async def create_item(item: Item):
await send_message()
return item
#bot.event
async def on_ready():
print(f'{bot.user.name} has connected to Discord!')
async def send_message():
user = await bot.fetch_user(USER_ID)
await user.send('👀')
if __name__ == "__main__":
import asyncio
print('Starting bot')
bot_app = bot.start(TOKEN)
bot_task = asyncio.ensure_future(bot_app)
print('Starting API')
uvicorn.run(app, host='0.0.0.0')

Python + Flask + Discord: How to send a message through discord through a flask endpoint?

I'm trying to send a message with discord, activated through a Flask endpoint
I get the following error message when I call http://127.0.0.1:5000/send
RuntimeError: There is no current event loop in thread 'Thread-4'.
I have the following (minimal) code
import discord
from flask import Flask, jsonify
async def my_background_task():
for message in ['a', 'b']:
await client.wait_until_ready()
channel = client.get_channel(CHANNEL_ID)
await channel.send(message)
await client.close()
def sendMessages():
client = discord.Client()
client.loop.create_task(my_background_task())
client.run('SECRET')
app = Flask(__name__)
#app.route('/send')
def send():
sendMessages()
Maybe you should consider using webhooks instead of a bot. Here is a simple example you should implement flask to it.
import requests #dependency
url = "<your url>" #webhook url, from here: https://i.imgur.com/aT3AThK.png
data = {}
#for all params, see https://discordapp.com/developers/docs/resources/webhook#execute-webhook
data["content"] = "message content"
data["username"] = "custom username"
#leave this out if you dont want an embed
data["embeds"] = []
embed = {}
#for all params, see https://discordapp.com/developers/docs/resources/channel#embed-object
embed["description"] = "text in embed"
embed["title"] = "embed title"
data["embeds"].append(embed)
result = requests.post(url, json=data, headers={"Content-Type": "application/json"})
try:
result.raise_for_status()
except requests.exceptions.HTTPError as err:
print(err)
else:
print("Payload delivered successfully, code {}.".format(result.status_code))
#result: https://i.imgur.com/DRqXQzA.png
Sometimes using webhooks cannot provide the required functionality.
If it is possible for you to switch from flask to quart, you can use the direct support for async methods to gain controll over the event loop as you intented to do in your given example.
The following snippet starts the discord bot within the same event loop as the quart server.
#app.before_serving
async def before_serving():
loop = asyncio.get_event_loop()
await client.login(TOKEN) # this could be done outside of this method
# do not use client.run
loop.create_task(client.connect())
A complete minimal working example would look like follows
import discord
import asyncio
from quart import Quart
app = Quart(__name__)
client = discord.Client()
#app.before_serving
async def before_serving():
loop = asyncio.get_event_loop()
await client.login(TOKEN)
loop.create_task(client.connect())
#app.route("/send", methods=["GET"])
async def send_message():
# wait_until_ready and check for valid connection is missing here
channel = client.get_channel(CH_ID)
await channel.send('XYZ')
return 'OK', 200
app.run()
The connect of the client could be triggered by the /send method itself, however it is important to not create a new task at every request.

How to call asyncio functions inside flask application in python

I have a flask application that receives the request and trying to take a screenshot from the given URL which is done with asyncio function.
What I have done is ,
import asyncio
from pyppeteer import launch
from flask import Flask
import base64
from flask import Blueprint, jsonify, request
import jwt
async def main():
browser = await launch(headless=True)
page = await browser.newPage()
await page.goto(target)
await page.screenshot({'path': '/tmp/screen.png', 'fullPage': True})
await browser.close()
app = Flask(__name__)
#app.route('/heatMapDbConfigSave', methods=['POST'])
def notify():
token, target,id = map(
request.form.get, ('token', 'target','id'))
asyncio.get_event_loop().run_until_complete(main(target))
if __name__ == '__main__':
app.run(host='localhost', port=5002, debug=True)
The problem I have faced is, getting error RuntimeError: There is no current event loop in thread 'Thread-2'. .I have googled and gone through previous posts. None helped and not pointed a clear solution.
What was the solution to solve this?
Thanks in advance!
you can try something like below
from flask import Blueprint
import asyncio
health_check = Blueprint('health_check', __name__)
async def first():
await asyncio.sleep(20)
return 'first'
async def second():
await asyncio.sleep(10)
return 'second'
async def third():
await asyncio.sleep(10)
return 'third'
def ordinary_generator():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
for future in asyncio.as_completed([first(), second(), third()]):
print('reached')
yield loop.run_until_complete(future)
#health_check.route('', methods=['GET'])
def healthcheck():
"""
Retrieves the health of the service.
"""
for element in ordinary_generator():
print(element)
return "Health check passed"
Blueprints are not required, but just used them. You have to register the blueprint in the main app file like below
app = Flask(__name__)
app.register_blueprint(health_check, url_prefix='/api/v1/healthcheck')
if __name__ == '__main__':
app.run()

Categories