Streamlit restful app

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.

4 Likes

Hi @Nelson_Silva

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.

7 Likes

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?

1 Like

Thanks @thiago I will try this :slight_smile:

@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 :slight_smile:

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.

5 Likes

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!

2 Likes

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 :slight_smile:

Thank you!

2 Likes

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?

3 Likes

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!)

5 Likes

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)

Hi @Heitor_Leal_Farnese,

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)

8 Likes

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!