Introduction
In the early stage of the project, I don’t want to introduce Vue, React, etc. to increase the complexity, and I don’t want to spend a lot of time on CSS and JS to achieve page layout and interaction. How can I quickly build a demo-level use case?
You can try streamlit. After using it for a while, I think it is a good tool. Well, it is open source: https://github.com/streamlit/streamlit
Moreover, the commercialization feels good, the community is more active, and the available components are more abundant.
![8c08e241ebdcf2051a53a344bbc497e0.png](https://img-blog.csdnimg.cn/img_convert/8c08e241ebdcf2051a53a344bbc497e0.png)
This article will first introduce the basic usage of Streamlit, then discuss how to implement login and registration, and finally mention its shortcomings in my eyes.
run streamlit
Streamlit's own positioning is a web prototyping tool for data scientists. Through streamlit, you can quickly launch your model or various data analysis reports.
Here, based on the streamlit tutorial document, understand the basic usage of streamlit. In fact, this is not the focus of this article, but for the sake of the integrity of the article, let me add it.
First, you need to install streamlit.
pip install streamlit
Because streamlit has many dependencies, it is recommended that you do it in a virtual environment.
After installation, you can create a hello world first, and create a hello.py file, the code is as follows:
import streamlit as st
st.title('Hello World!')
run up:
streamlit run hello.py
The effect is as follows:
![09786d0bc5f710729a4114046acffabd.png](https://img-blog.csdnimg.cn/img_convert/09786d0bc5f710729a4114046acffabd.png)
Streamlit provides a lot of components for you to develop beautiful pages. Regarding how to use it, the documentation of streamlit is already well written. It doesn’t take much to discuss this here. Just read the documentation: https://docs. streamlit.io/library/get-started/create-an-app.
This article mainly talks about what is not in the document.
How does streamlit start?
The running mode of streamlit is to use the form of streamlit run xxx.py. After reading the document, it is found that it also supports the form of python -m streamlit run xxx.py.
I feel that the sense of control is not strong. How does the streamlit run command work?
Pull down the streamlit source code and see the entry_points configuration in its setup.py file.
![538f11d1e83a3b1ac9f698b10419b3d7.png](https://img-blog.csdnimg.cn/img_convert/538f11d1e83a3b1ac9f698b10419b3d7.png)
As can be seen from the figure above, the function of the streamlit command comes from the main() method of streamlit/web/cli.py.
![71b5f0470bb79e2b6b6e98a27d975201.png](https://img-blog.csdnimg.cn/img_convert/71b5f0470bb79e2b6b6e98a27d975201.png)
Well, use the click library to receive command parameters. To put it bluntly, I feel that the way the click library receives command line parameters is much more elegant than Python's native argparse.
Simply read the code of cli.py, you can find that streamlit is developed based on tornado, using tornado.httpserve as the web backend.
![2cd45ca9372bdfff85f166150c80a45e.png](https://img-blog.csdnimg.cn/img_convert/2cd45ca9372bdfff85f166150c80a45e.png)
Tornado is an old web framework in Python. At that time, python asyncio was not yet mature. In order to obtain a high-performance web framework, tornado realized its high-performance asynchronous io based on Linux epoll and BSD kqueue, and its source code was complicated and difficult. Understand.
After reading the code, it can be found that streamlit will first load index.html, and then embed your page logic in index.html (that is, the page created by using the components provided by streamlit). The specific embedding location is the div element whose id is root .
![6999cf8130fe089957f62ca5bd331dfb.png](https://img-blog.csdnimg.cn/img_convert/6999cf8130fe089957f62ca5bd331dfb.png)
This is the basic process of streamlit.
Eliminate the streamlit flag
When we implement hello.py and run it, we will find that there are many features of streamlit, as shown in the following figure:
![b60866e0a851aeee16a08607b8aa077e.png](https://img-blog.csdnimg.cn/img_convert/b60866e0a851aeee16a08607b8aa077e.png)
As can be seen from the figure above, streamlit provides the operations of setting, reloading, and accessing the official website of streamlit. In addition, streamlit has added its own streamlit logo on the title and footer.
When we need to launch our own web app, of course we don't want users to have so many operations, and we don't want competitors to see that I use streamlit at a glance.
The solution is to hide these contents. When we search for [streamlit hiddle xxx], we will find the following solution: https://discuss.streamlit.io/t/remove-made-with-streamlit-from-bottom-of -app/1370/2, the relevant code is as follows:
hide_streamlit_style = """
<style>
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
</style>
"""
st.markdown(hide_streamlit_style, unsafe_allow_html=True)
The principle is very simple, execute html code through the markdown method of st, and use css to hide these things.
Unfortunately, this effect is not good. When streamlit loads, it will first load its own html and js, and then load your logic. When the network is poor, the menu and footer will be displayed for a while, and then your css hide.
In addition, streamlit will reload the page every time the user operates, such as clicking a button on the page. It is still the old process. It first loads its own html, css, js, and then loads yours. For every operation, the menu and footer will appear on the page, which is deceptive.
The best way, of course, is to directly modify the streamlit source code and delete all unnecessary parts.
Reading the source code of streamlit, we can see that the front end of streamlit is implemented by React. When it is released as a python library, the code implemented by React is packaged by webpack. If you want to modify the source code, you need to modify the React code, and then go through the packaging and publishing process yourself.
Well, the cost is a bit high, and after I modify it myself, it will be difficult to be compatible with the new features of streamlit later. After simple thinking, I will use a hard replacement method to do it.
Create init_streamlit.py and write the following code:
import pathlib
import os
from bs4 import BeautifulSoup
from shutil import copyfile
from configs import ROOT_PATH
def modify_title_str(soup, title):
"""
修改 index.html 的 title
"""
soup.title.string = title
return soup
def add_js_code(soup, js_code):
"""
添加 js code 到 index.html 中
"""
script_tag = soup.find(id='custom-js')
if not script_tag:
script_tag = soup.new_tag("script", id='custom-js')
# custom-js script 中的 js code
script_tag.string = js_code
# 向 body 节点中添加内容
soup.body.append(script_tag)
return soup
def replace_favicon(streamlit_model_path):
"""替换streamlit的icon"""
logo_path = os.path.join(streamlit_model_path, 'static', 'favicon.png')
# 删除 logo
pathlib.Path(logo_path).unlink()
copyfile(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'favicon.png'), logo_path)
def init_streamlit(streamlit_model_path, title, footer):
index_path = pathlib.Path(streamlit_model_path) / "static" / "index.html"
soup = BeautifulSoup(index_path.read_text(encoding='utf-8'), features="lxml")
soup = modify_title_str(soup, title)
js_code = f'''
document.querySelector("#MainMenu").style.visibility = 'hidden'
document.querySelector('footer').innerHTML = '{footer}'
'''
soup = add_js_code(soup, js_code)
index_path.write_text(str(soup), encoding='utf-8')
streamlit_model_path = os.path.join(ROOT_PATH, 'venv\\lib\\site-packages\\streamlit')
init_streamlit(streamlit_model_path=streamlit_model_path, title='懒编程', footer='Copyright © 2022, ayuliao Inc.')
The above code is mainly to replace the relevant elements of index.html in streamlit, such as title, footer, etc., by directly modifying index.html to achieve the effect of hiding relevant information of streamlit, so that streamlit will not load its own html first, js and there is a problem that these elements cannot be well hidden.
In addition, simply modifying the title of index.html has no effect. The reason is that the title in index.html will also be modified by the js method of streamlit itself. To solve this problem, you can modify the hello.py file. The code is as follows:
import init_streamlit
import streamlit as st
st.set_page_config(page_title='懒编程',
page_icon='logo.jpg')
st.title('hello world')
Run hello.py, the effect is as shown in the figure below:
![72ac2957d5ad087a61145384a5d28f10.png](https://img-blog.csdnimg.cn/img_convert/72ac2957d5ad087a61145384a5d28f10.png)
Realize login and registration
How to implement login and registration is also something that cannot be seen in the document.
Streamlit itself does not provide functions such as login and registration, which may be related to the positioning of streamlit itself. To achieve login and registration, we need to write it ourselves and implement it in the form of a streamlit plug-in.
streamlit has a plug-in page, which provides excellent streamlit plug-ins. Most of the front-ends of streamlit plug-ins are implemented by themselves using React, but React uses the methods provided by streamlit to achieve the purpose of streamlit plug-ins.
Of course, some simple plug-ins do not necessarily need to interact with the React development page, as is the case with login and registration plug-ins.
After searching, I found the Streamlit-Authenticator plug-in (https://github.com/mkhorasani/Streamlit-Authenticator), which can be installed and used through pip:
pip install streamlit-authenticator
Because the functions provided by streamlit-authenticator are too simple, it does not record user information through the database, and in most cases, it cannot satisfy us, so we need to make magic changes to it.
To run the source code of streamlit-authenticator normally, you need to install related dependencies, but streamlit-authenticator does not provide a requirements.txt file, but its setup.py gives the dependencies, you can base yourself on the information in setup.py Install or use the lazy method like me, first install streamlit-authenticator, and then delete it separately, so that the relevant dependencies are installed.
I put streamlit-authenticator related code in the libs folder.
streamlit-authenticator originally implemented login and registration through yaml configuration files. I changed it to use sqllite. Of course, you can change it to MySQL and so on. In addition, I added the logic of the invitation code. Here I hard-coded some invitation codes. Only users with these invitation codes can register, and registered users can log in.
In order to cooperate with the modified streamlit-authenticator, I created the models directory and wrote the relevant sql logic in it.
Well, this one is not complicated, but there are many logics for changes, so I won’t describe it in words. Turn to the end of the article and look at the project code.
Due to the refresh mechanism of streamlit (the page will be refreshed every time a button is operated on the page), there are also pitfalls in how to reasonably organize the login, registration and login pages.
If you look at the contents of the multi-page app in the streamlit official document, you will find that the layout is very rigid and ugly. After simple experiments and research, I used the tabs component to achieve the final layout. The relevant code is as follows:
import os
import yaml
import init_streamlit
import streamlit as st
import libs.streamlit_authenticator as stauth
st.set_page_config(page_title='懒编程',
page_icon='logo.jpg')
st.title('hello world')
def init_authenticator():
filepath = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(filepath, 'auth.yaml')) as file:
config = yaml.load(file, Loader=stauth.SafeLoader)
authenticator = stauth.Authenticate(
config['credentials'],
config['cookie']['name'],
config['cookie']['key'],
config['cookie']['expiry_days'],
)
return authenticator
def register_user(authenticator):
try:
if authenticator.register_user('Register user', preauthorization=False):
st.success('User registered successfully')
except Exception as e:
st.error(e)
def my_logics():
st.markdown('login success')
def start_web():
authenticator = init_authenticator()
# check cookie not login again
authenticator._check_cookie()
if st.session_state["authentication_status"]:
authenticator.logout('Logout', 'sidebar')
my_logics()
else:
tab1, tab2 = st.tabs(["Login", "Register"])
with tab1:
name, authentication_status, username = authenticator.login(
'Login', 'main')
if st.session_state["authentication_status"] == False:
st.error('Username/password is incorrect')
elif st.session_state["authentication_status"] == None:
st.warning('Please enter your username and password')
with tab2:
register_user(authenticator)
start_web()
Login page:
![6b767f9a3b0f78cb1f1138677b2d11fd.png](https://img-blog.csdnimg.cn/img_convert/6b767f9a3b0f78cb1f1138677b2d11fd.png)
Registration page:
![0d6120f9951bb9e8f3f3e684922977b9.png](https://img-blog.csdnimg.cn/img_convert/0d6120f9951bb9e8f3f3e684922977b9.png)
Home page after successful login:
![13cc8c9a2c3ee49c250062db6c3113a9.png](https://img-blog.csdnimg.cn/img_convert/13cc8c9a2c3ee49c250062db6c3113a9.png)
end
Using streamlit, we can quickly build a web demo that can be shown to others, but streamlit also has a relatively big flaw in my eyes, that is, there is no function to distinguish requests, such as Flask, Fastapi and other frameworks, you can distinguish Different requests, but streamlit does not work. When multiple people use it, it will appear that when others are operating the page, your current page may also be affected.
Well, this is the practice related to streamlit, the relevant code github in this article: https://github.com/ayuLiao/learn-streamlit
I'm Erliang, see you in the next article.