Is there a way to create a restful app using only streamlit, like Flask?
Or can you provide such an example using Flask? This would be very helpful.
I am loving Streamlit so far.
Thatās an awesome idea. Streamlit doesnāt support that use case at the moment, and after looking at the internals of our code I donāt see a good way to hack it together either.
So I opened a feature request here: https://github.com/streamlit/streamlit/issues/439
I said I donāt see a good way to hack it, because a nasty hack would be to hijack one of Streamlitās āscript threadsā to serve your Flask app. I have no idea how well that would perform, though. Use at your own risk!
import streamlit as st
if not hasattr(st, 'already_started_server'):
# Hack the fact that Python modules (like st) only load once to
# keep track of whether this file already ran.
st.already_started_server = True
st.write('''
The first time this script executes it will run forever because it's
running a Flask server.
Just close this browser tab and open a new one to see your Streamlit
app.
''')
from flask import Flask
app = Flask(__name__)
@app.route('/foo')
def serve_foo():
return 'This page is served via Flask!'
app.run(port=8888)
# We'll never reach this part of the code the first time this file executes!
# Your normal Streamlit app goes here:
x = st.slider('Pick a number')
st.write('You picked:', x)
If you run the code above, you can then go to localhost:8888/foo
to see some data served via Flask.
I like this workaround, I should try this on my existing app, I like dual apps like this - is there any technical downside to this from REST point of view or websockets or serving point of view?
Thanks @thiago I will try this
@neomatrix369 besides some code style complexity, I think the main downside is just performance. Both servers are running on the same Python process, which means they share the same Global Interpreter Lock.
@Nelson_Silva Please let us know if that worked or not for you
As streamlit is running on Tornado my guess there would be some Tornado way of offering rest api endpoints.
I would really like this functionality in order to be able to serve csv, excel, parquet etc. files for my users.
This loads flask in 8888 and streamlit in 8501. can then run in same port with different endpoints ?
My guess is thatās not really possible. I donāt think Flask and Tornado (which Streamlit uses) were written with that kind of use case in mind.
That said, weāre actually designing some solutions for the use case @Marc mentioned above, where the developer wants to serve files. So stay tuned!
I kinda achieved what i want with hosting in different ports but using F5 to show it as if they are in different endpoints of a single domain
I hope that it can be done.
If itās possible, I think streamlit is really l33t. Iām enjoying deploying my models in AWS without the hassle of flask and I can edit the codes at VSCODE and view the output once I ctrl-S
Thank you!
Iād love to see that too! Itād be even better if your REST API could be auto generated like @thiago suggested, for instance from your widgets and variables.
Hi. Is there any chance of seeing this functionality?
I believe not having to maintain two separate servers for API and demo would be of very significant value, no?
How does streamlit accomplish the app widgets without building some sort of internet facing API from the functions in the notebook?
Try this as a alternative. Let me know if you likeā¦
SQLalchemy + Python Tutorial (using Streamlit)
I couldnāt get this working with out-of-the-box streamlit 1.7, but hacked at two solutions for this last night. One hard-coded on init and one dynamic at run-time. Comment on Github
The dynamic way is literally a dictionary mapping to functions, so there is probably a better way to manage the safety / sanity of itā¦
Basic steps I took:
- In
lib/streamlit/server/server.py
add at least one route in_create_app
(this list gets passed to tornado app initialization):
(make_url_path_regex(base, "api/.*"), StreamlitAPIHandler)
- Implement matching Handler class(es)
# Dynamic
class StreamlitAPIHandler(tornado.web.RequestHandler):
def get(self, path):
fn = Server.get_current()._api_map.get(path, default_api_handler)
result = fn(self.request.arguments)
self.write({'message': result})
# Static
class StreamlitAPIHandler(tornado.web.RequestHandler):
def get(self):
args = ', '.join(f"{key}: {','.join(x.decode() for x in val)}" for key, val in self.request.arguments.items())
self.write(f"Hello, Streamlit API User! {args}!")
- (Dynamic case) Add routes dynamically in streamlit script by adding to a mapping maintained in the Server object. (Could be aliased to
st.route
from thiagoās API proposals?)
import streamlit as st
from streamlit.server.server import Server
import httpx
def handle_balloons(args):
return f"š BALLOONS: {args}"
Server.get_current().add_api_route('balloons', handle_balloons)
result = httpx.get('http://localhost:8501/api/balloons')
st.write(result)
st.write(result.text)
In either case using requests
/ httpx
will allow the app to hit the endpoint itself, or can call the handle_balloons()
directly because itās already within the app (This demo is kind of useless).
From outside the streamlit app they can interact with handle_balloons()
at http://localhost:8501/api/balloons
as well (thatās the fun new part!)
Class Server
has no method add_api_route
(Streamlit 1.10.0).
I like your solution, @thiago ! Thank you for that!
Building on what you started, this is how I make the first page auto-refresh, so that /foo is created (on port 8888) while you go straight to your Streamlit app without having to go through the manual close-tap-open-tab routine.
import streamlit as st
import multiprocessing
must_reload_page = False
def start_flask():
if not hasattr(st, 'already_started_server'):
st.already_started_server = True
must_reload_page = True
from flask import Flask
app = Flask(__name__)
@app.route('/foo')
def serve_foo():
return 'This page is served via Flask!'
app.run(port=8888)
def reload_page():
if must_reload_page:
must_reload_page = False
st.experimental_rerun()
if __name__=='__main__':
flask_process = multiprocessing.Process(target=start_flask)
reload_process = multiprocessing.Process(target=reload_page)
flask_process.start()
reload_process.start()
# Your normal Streamlit app goes here:
x = st.slider('Pick a number')
st.write('You picked:', x)
Thanks for posting your solution as itās useful to see how folks are using Streamlit. Unfortunately, I couldnāt get it to work well in Python 3.9.13 and Streamlit 1.13.0.
The Flask server restarted every time I interacted with the slider. I was also trying to use st.session_state
in the app (nothing to do with the Flask server) and I was getting errors like: WARNING streamlit.runtime.state.session_state_proxy: Session state does not function when running a script without streamlit run
. I think this is because Flask is being forked to run as a sub-process and it drags st
into its runtime context, and st
complains. This would suggest itās better to create a separate flask_runner.py
file which doesnāt depend on on st
, and run it from your app using subprocess.Popen(job)
where job
is the command line python flask_runner.py
.
Iāve posted a gist of my implementation so you can see how I got around the issues. I also have a more complete implementation of this use case, using FastAPI instead of Flask. Check it out here: GitHub - asehmi/simple-streamlit-fastapi-integration: A minimal Streamlit app showing how to launch and stop a FastAPI process on demand.
'st wishes,
Arvindra
Hi,
I have been able to had a custom REST API to the internal Tornado server, at runtime, with the following code.
Hope this helps :
# This code adds custom REST api handler at runtime to a running Streamlit app
#
from tornado.web import Application, RequestHandler
from tornado.routing import Rule, PathMatches
import gc
import streamlit as st
@st.cache_resource()
def setup_api_handler(uri, handler):
print("Setup Tornado. Should be called only once")
# Get instance of Tornado
tornado_app = next(o for o in gc.get_referrers(Application) if o.__class__ is Application)
# Setup custom handler
tornado_app.wildcard_router.rules.insert(0, Rule(PathMatches(uri), handler))
# === Usage ======
class HelloHandler(RequestHandler):
def get(self):
self.write({'message': 'hello world'})
# This setup will be run only once
setup_api_handler('/api/hello', HelloHandler)
Thanks @raphaeljolivet for this example. Seems to work great (with Steamlit v1.28) and I was able to wrap and implement routes as a decorator.
I wish Streamlit would expose the server and application instance directly!