Creating Multiple Instances of Python Class in Flask - python

I have made a flask application which gets and receives messages from the user and generates a reply from a Chatbot backend I have created. When I run my my app.py file and I go to my localhost it runs fine if I only open one instance, however if I try to open multiple instances then they all try to use the same bot. How do I create a unique bot for each session. I tried using g.bot = mybot() but the problem was it still kept creating a new bot each time the user replied to the bot. I am relatively new to this so links to a detailed explanation would be appreciated. Note some pieces of code are unrelated junk from previous versions.
app = Flask(__name__)
items = ["CommonName","Title",
"Department","Address","City","PhoneNum"]
app.config.from_object(__name__)
bot2 = Bot2()
#app.before_request
def before_request():
session['uid'] = uuid.uuid4()
print(session['uid'])
g.bot2 = Bot2()
#app.route("/", methods=['GET'])
def home():
return render_template("index.html")
#app.route("/tables")
def show_tables():
data = bot2.df
if data.size == 0:
return render_template('sad.html')
return render_template('view.html',tables=[data.to_html(classes='df')], titles = items)
#app.route("/get")
def get_bot_response():
userText = request.args.get('msg')
bot2.input(str(userText))
print(bot2.message)
g.bot2.input(str(userText))
print(g.bot2.message)
show_tables()
if (bot2.export):
return (str(bot2.message) + "<br/>\nWould you like to narrow your results?")
#return (str(bot.message) + "click here" + "</span></p><p class=\"botText\"><span> Would you like to narrow your results?")
else:
return (str(bot2.message))
if __name__ == "__main__":
app.secret_key = 'super secret key'
app.run(threaded=True)

Problem: A new bot is created each time the user replies to the bot
Reason: app.before_request runs before each request that your flask server receives. Hence each reply will create a new Bot2 instance. You can read more about it here.
Problem: Creating a bot per instance
Possible Solution: I'm not really sure what you mean by opening multiple instances (are you trying to run multiple instances of the same server, or there are multiple ppl accessing the single server). I would say to read up on Sessions in Flask and store a Bot2 instance inside sessions as a server side variable. You can read more about it here and here.

You could try assigning the session id from flask-login to the bot instead of creating a unique id at before request. Right now, like #Imma said, a new bot is created for every request.
You will have to store an array of classes. When a session is created, i.e., a user or an anonymous user logs in, a bot/class instance gets created and is pushed onto the array.
You can keep the array in the session object... do note that the session object will get transferred in the form of a cookie to the front end... so you may be potentially exposing all the chat sessions...Also, a lot of users will unnecessarily slow down the response.
Another alternative is to create a separate container and run the bot as a separate microservice rather than integrate it with your existing flask application (this is what we ended up doing)

Delete the line bot2 = Bot2(), and change all the reference to bot2 to g.bot2.

Related

Flask send JSON to the front end within a python function from the backend that also needs to return a `send_file()`?

I'm trying to see if there is a simple way to send information to the front end of my website within a function from the backend in my python code that needs to return a send_file() call at the end. I'm trying to download files from YouTube using pytube and then send those videos to the end user. However, during the process of downloading said videos, I want to show the progress of the function that's running by passing a percent of completion number to the front end.
This is basically what I have.
#app.route('/main', methods = ['GET', 'POST'])
def main():
if request.method == 'POST':
files = get_files(current_user)
return files or redirect(url_for('home'))
return render_template('main.html', user = current_user)
def get_files(user):
if not user:
return None:
zip_bytes = BytesIO()
counter = 0
with ZipFile(zip_bytes, "w") as zip:
while True:
counter++
#send counter value to front-end to update progress bar
#code for generating files and adding them into the zip file
return send_file(zip_bytes, download_name=f"something.zip", as_attachment=True)
So what I want to do, is to send the counter over to my front-end, to change some HTML on my page to say something like "3/10 files completed", "4/10 files completed", etc.
If possible, please explain it in simple terms because I don't know much terminology in the world of web development.
You need a table to keep track of tasks with the following rough attributes
id
uuid
percentage_completed
Then, in the function the task is being processed, add a record to the tasks table with percentage 0, return the task id to the front-end so that it can ping for status. Update task percentage after each video
# pseudo code
#app.route('/some-url')
def function():
uuid = random_string
task = Task(uuid, percentage_completed=0)
db.session.add(task)
subprocess run download_vids
return jsonify({'task_id': uuid})
def download_vids(uuid):
for i, vid in enumerate(vids):
# do stuff
task = db.query(Task).filter_by(uuid=uuid).first()
task.percentage_completed = (i+1//len(vids) // 100
db.session.commit()
# db.session.delete(task)
# db.session.commit()
#app.route('/progress/<task_id>') # front-end will ping this each time
def return_stats(task_id):
task = db.query(Task).filter_by(uuid=uuid).first()
return jsonify({'progress': task.percentage_completed})
This answer uses sqlalchemy for database transactions.
You might want to have a look at celery for better handling of tasks.

how to hold new request when old request is processing in flask?

I created a python app in flask. Here the Skelton of the code
app = Flask(__name__)
#app.route('/', methods=['GET'])
def authentication():
'''athentication process'''
return 'authenticated'
so when user call the app it will authenticate. but if two user call that at same time or while processing one authentication i want to hold the new request until the old one finished then I want to start the new request. I've tried with semaphore but not working. Here is what I've tried
#app.route('/', methods=['GET'])
def authentication():
sem.acquire()
'''athentication process'''
sem.release()
return 'authenticated'
and I have deployed this in Heroku. Any idea how I can achieve this?
PS: If this can't be done at least i want to response the new request that another request is in process and try again after some time
Short answer: Dont worry about it.
This is the job of a web server. When you host the application in any Server like Apache , Nginx etc the server creates multiple processes of your flask app.When requst comes the, server program forwards it to any of the free processes, if no process is free server will queue the request until one process becomes free.
This is high level overwiew of how HTTP servers work.

How can I serve stored data from web-sockets using Flask?

TLDR; I am trying to run a client that fetches data from a web-sockets URI, and then use Flask to serve the fetched data from the sockets.
My intended workflow looks like: Read data from web-sockets (async) -> write data to a Python dict (continuously) -> read data from Python dict using GET requests on Flask (continuously)
The problem I am facing is that I am using a Python dictionary for storage, but when I read that dictionary from Flask, it does not show me updated values.
I have created an illustration with code to my problem
Client:
import asyncio
import websockets
import json
SOME_URI = "ws://localhost:8080/foo"
connections = set()
connections.add(SOME_URI)
class Storage:
storage = dict() # local storage dict for simplicity
#staticmethod
def store(data): # here I store the value
a, b, c = data
Storage.storage[a] = c
#staticmethod
def show(): # Here I just show the value to be used in the GET
return Storage.storage
async def consumer_handler(uri):
async with websockets.connect(uri) as websocket:
async for message in websocket:
await consumer(message)
async def consumer(message):
line = json.loads(message)
Storage.store(line) # adds message to dict
async def main():
await asyncio.wait([consumer_handler(uri) for uri in connections])
if __name__ == "__main__":
asyncio.run(main())
App:
from flask import Flask
from client import Storage
app = Flask(__name__)
app.debug = True
#app.route('/bar', methods=['GET'])
def get_instruments():
res = Storage.show() # I expected here to see updated value for the dict as it fills up from websockets
return res, 200
if __name__ == "__main__":
app.run()
Whenever I try to make a GET request to the Flask page, I get the value of an Empty dict (does not reflect the changes I add from the web socket). I was expecting to get an updated value of the dict with every GET request.
The problem is that you have two separate Python processes involved here -- one is the async script for ingesting data and the other is running flask. As these are separate processes, the dict updates that you're doing on the first process is not visible to the second process.
You need some form of IPC (Inter-Process Communication) mechanism here for data portability and/or persistence. One obvious and easier option to try is to use a named FIFO or a plain file. Depending on the level of complexity you want to handle for this, using Redis (or alike) is also another option.

How can i subscribe a consumer and notify him of any changes in django channels

I'm currently building an application that allows users to collaborate together and create things, as I require a sort of discord like group chatfeed. I need to be able to subscribe logged in users to a project for notifications.
I have a method open_project that retrieves details from a project that has been selected by the user, which I use to subscribe him to any updates for that project.
So I can think of 2 ways of doing this. I have created a instance variable in my connect function, like this:
def connect(self):
print("connected to projectconsumer...")
self.accept()
self.projectSessions = {}
And here is the open_project method:
def open_project(self, message):
p = Project.objects.values("projectname").get(id=message)
if len(self.projectSessions) == 0:
self.projectSessions[message] = []
pass
self.projectSessions[message] = self.projectSessions[message].append(self)
print(self.projectSessions[message])
self.userjoinedMessage(self.projectSessions[message])
message = {}
message["command"] = "STC-openproject"
message["message"] = p
self.send_message(json.dumps(message))
Then when the user opens a project, he is added to the projectSessions list, this however doesn't work (I think) whenever a new user connects to the websocket, he gets his own projectconsumer.
The second way I thought of doing this is to create a managing class that only has 1 instance and keeps track of all the users connected to a project. I have not tried this yet as I would like some feedback on if I'm even swinging in the right ball park. Any and all feedback is appreciated.
EDIT 1:
I forgot to add the userjoinedMessage method to the question, this method is simply there to mimic future mechanics and for feedback to see if my solution actually works, but here it is:
def userjoinedMessage(self, pointer):
message = {}
message["command"] = "STC-userjoinedtest"
message["message"] = ""
pointer.send_message(json.dumps(message))
note that i attempt to reference the instance of the consumer.
I will also attempt to implement a consumer manager that has the role of keeping track of what consumers are browsing what projects and sending updates to the relevant channels.
From the question, the issue is how to save projectSessions and have it accessible across multiple instances of the consumer. Instead of trying to save it in memory, you can save in a database. It is a dictionary with project as key. You can make it a table with ForeignKey to the Project model.
In that way, it is persisted and there would be no issue retrieving it even across multiple channels server instances if you ever decide to scale your channels across multiple servers.
Also, if you feel that a traditional database will slow down the retrieval of the sessions, then you can use faster storage systems like redis
Right, this is probably a horrible way of doing things and i should be taken out back and shot for doing it but i have a fix for my problem. I have made a ProjectManager class that handles subscriptions and updates to the users of a project:
import json
class ProjectManager():
def __init__(self):
if(hasattr(self, 'projectSessions')):
pass
else:
self.projectSessions = {}
def subscribe(self, projectid, consumer):
print(projectid not in self.projectSessions)
if(projectid not in self.projectSessions):
self.projectSessions[projectid] = []
self.projectSessions[projectid].append(consumer)
self.update(projectid)
def unsubscribe(self, projectid, consumer):
pass
def update(self, projectid):
if projectid in self.projectSessions:
print(self.projectSessions[projectid])
for consumer in self.projectSessions[projectid]:
message = {}
message["command"] = "STC-userjoinedtest"
message["message"] = ""
consumer.send_message(json.dumps(message))
pass
in my apps.py file i initialize the above ProjectManager class and assign it to a variable.
from django.apps import AppConfig
from .manager import ProjectManager
class ProjectConfig(AppConfig):
name = 'project'
manager = ProjectManager()
Which i then use in my consumers.py file. I import the manager from the projectconfig class and assign it to a instance variable inside the created consumer whenever its connected:
def connect(self):
print("connected to projectconsumer...")
self.accept()
self.manager = ProjectConfig.manager
and whenever i call open_project i subscribe to that project with the given project id recieved from the front-end:
def open_project(self, message):
p = Project.objects.values("projectname").get(id=message)
self.manager.subscribe(message, self)
message = {}
message["command"] = "STC-openproject"
message["message"] = p
self.send_message(json.dumps(message))
as i said i in no way claim that this is the correct way of doing it and i am also aware that channel_layers supposedly does this for you in a neat way. i however don't really have the time to get into channel_layers and will therefore be using this.
I am still open to suggestions ofcourse and am always happy to learn more.

Unable to redirect to method url through action prop in Gather method twilio

I have been trying to create a flow for sending back modified output to user on basis of the key press.
For this, I am using the twilio's Api.
Before I go ahead with my problem I will share the two pieces of code from Route.py and CallResponse.py
app = Flask(__name__)
logger = lh.log_initializer()
#app.route("/Aresponse", methods=['GET', 'POST'])
def ReceivedA():
"""Respond to incoming requests."""
logger.debug("Starting ReceivedA flow| name:" + __name__)
resp = tcr.RoutedReceivedA()
return str(resp)
if __name__ == "__main__":
# logger.debug("In routing main")
app.run(debug=True, host='0.0.0.0', port=80, threaded=True)
Below is the code of CallResponse.py
app = Flask(__name__)
logger = lh.log_initializer()
def RoutedReceivedA():
"""Respond to incoming requests."""
resp = VoiceResponse()
resp.say("Hello Monkey")
# Say a command, and listen for the caller to press a key. When they press
# a key, redirect them to /user-input-key.
logger.debug('Starting call receiving process|name:' + __name__)
A_num = Gather(numDigits=2, action="/user-A-input", method="POST")
A_num_entered = str(A_num) #there is surely something not right with this line, but I am hardly bothered about this right now
A_num.say("Please press a key to register your response")
logger.debug("After taking A number from user| A num:" + A_num_entered)
resp.append(A_num)
return str(resp)
#app.route("/user-A-input", methods=['GET', 'POST'])
def UserAInput():
"""Handle key press from a user."""
# Get the digit pressed by the user
#The below logs are not printed, so somehow this method isn't called
logger.debug('before considering user input in route')
route_num = request.values.get('Digits', None)
logger.debug('In side UserRouteInput| route_num:' + route_num)
return route_num
So when I start the app, I hit the url http://xyz.abc.io/AResponse, the request is first received in the Route.py to the ReceivedA() method, which internally calls the RoutedReceivedA(), which prints the log
Starting call receiving process|name:...
after which I am expecting it to redirect to the method UserAInput, as I am passing the method URL through the action property in Gather method of twilio library, but it isn't calling that method. To confirm that I have also written a debug log, which never prints itself.
The later pain would be to get the user inputted value and pass it to another method (as it would make an api call on basis of that input) which I am not bothered about right now.
I have tried understanding the flask working to verify if there is something I am doing wrong with that. I am missing something here but unknowingly I am not able to understand what.
Although I am trying to find a solution myself, I can surely use some help.
P.S: I tried replicating the issue using the sample gather code on twilio website, by creating a maincall.py which internally calls hello_monkey() method of the twilios sample code, which then calls handle-key. Even in this case it wasn't routing to the handle_key() method. However it was working when I was directly calling the hello_monkey() through the API call. I believe I am surely misunderstanding some concept of flask routing. Really frustrated.
I found the solution to the problem. I need to
#app.route("/user-A-input", methods=['GET', 'POST'])
method in Route.py instead of CallResponse.py
It's probably because it is using the instance of flask object which was created in the Route.py. I still need to look into detail, like how can I make it work by keeping the UserAInput method in CallResponse.py

Categories