90行Python代碼開發(fā)個(gè)人云盤應(yīng)用
本文示例代碼已上傳至我的Github倉庫https://github.com/CNFeffery/DataScienceStudyNotes
1 簡(jiǎn)介
在今天的教程中,我們將介紹如何在Dash中高效地開發(fā)web應(yīng)用中非常重要的「文件上傳」及「下載」功能。
2 在Dash中實(shí)現(xiàn)文件上傳與下載
2.1 在Dash中配合dash-uploader實(shí)現(xiàn)文件上傳
其實(shí)在自帶的dash_core_components中就封裝了基于html5原生API的dcc.Upload()組件,可以實(shí)現(xiàn)簡(jiǎn)單的文件上傳功能,但說實(shí)話,非常的「不好用」,其主要缺點(diǎn)有:
- 「文件大小有限制,150M到200M左右即出現(xiàn)瓶頸」
- 「策略是先將用戶上傳的文件存放在瀏覽器內(nèi)存,再通過base64形式傳遞到服務(wù)端再次解碼,非常低效」
- 「整個(gè)上傳過程無法配合準(zhǔn)確的進(jìn)度條」
正是因?yàn)镈ash自帶的上傳部件如此不堪,所以一些優(yōu)秀的第三方拓展涌現(xiàn)出來,其中最好用的要數(shù)dash-uploader,它解決了上面提到的dcc.Upload()的所有短板。通過pip install dash-uploader進(jìn)行安裝之后,就可以直接在Dash應(yīng)用中使用了。
我們先從極簡(jiǎn)的一個(gè)例子出發(fā),看一看在Dash中使用dash-uploader的正確姿勢(shì):
app1.py
import dash import dash_uploader as du import dash_bootstrap_components as dbc import dash_html_components as html app = dash.Dash(__name__) # 配置上傳文件夾 du.configure_upload(app, folder='temp') app.layout = html.Div( dbc.Container( du.Upload() ) ) if __name__ == '__main__': app.run_server(debug=True)
可以看到,僅僅十幾行代碼,我們就配合dash-uploader實(shí)現(xiàn)了簡(jiǎn)單的文件上傳功能,其中涉及到dash-uploader兩個(gè)必不可少的部分:
2.1.1 利用du.configure_upload()進(jìn)行配置
要在Dash中正常使用dash-uploader,我們首先需要利用du.configure_upload()進(jìn)行相關(guān)配置,其主要參數(shù)有:
「app」,即對(duì)應(yīng)已經(jīng)實(shí)例化的Dash對(duì)象;
「folder」,用于設(shè)置上傳的文件所保存的根目錄,可以是相對(duì)路徑,也可以是絕對(duì)路徑;
「use_upload_id」,bool型,默認(rèn)為True,這時(shí)被用戶上傳的文件不會(huì)直接置于「folder」參數(shù)指定目錄,而是會(huì)存放于du.Upload()部件的upload_id對(duì)應(yīng)的子文件夾之下;設(shè)置為False時(shí)則會(huì)直接存放在根目錄,當(dāng)然沒有特殊需求還是不要設(shè)置為False。
通過du.configure_upload()我們就完成了基本的配置。
2.1.2 利用du.Upload()創(chuàng)建上傳部件
接下來我們就可以使用到du.Upload()來創(chuàng)建在瀏覽器中渲染供用戶使用的上傳部件了,它跟常規(guī)的Dash部件一樣具有「id」參數(shù),也有一些其他的豐富的參數(shù)供開發(fā)者充分自由地自定義功能和樣式:
「text」,字符型,用于設(shè)置上傳部件內(nèi)顯示的文字;
「text_completed」,字符型,用于設(shè)置上傳完成后顯示的文字內(nèi)容前綴;
「cancel_button」,bool型,用于設(shè)置是否在上傳過程中顯示“取消”按鈕;
「pause_button」,bool型,用于設(shè)置是否在上傳過程中顯示“暫?!卑粹o;
「filetypes」,用于限制用戶上傳文件的格式范圍,譬如['zip', 'rar', '7zp']就限制用戶只能上傳這三種格式的文件。默認(rèn)為None即無限制;
「max_file_size」,int型,單位MB,用于限制單次上傳的大小上限,默認(rèn)為1024即1GB;
「default_style」,類似常規(guī)Dash部件的style參數(shù),用于傳入css鍵值對(duì),對(duì)部件的樣式進(jìn)行自定義;
「upload_id」,用于設(shè)置部件的唯一id信息作為du.configure_upload()中所設(shè)置的緩存根目錄的下級(jí)子目錄,用于存放上傳的文件,默認(rèn)為None,會(huì)在Dash應(yīng)用啟動(dòng)時(shí)自動(dòng)生成一個(gè)隨機(jī)值;
「max_files」,int型,用于設(shè)置一次上傳最多可包含的文件數(shù)量,默認(rèn)為1,也推薦設(shè)置為1,因?yàn)槟壳皩?duì)于多文件上傳仍有「進(jìn)度條異?!?、「上傳結(jié)束顯示異常」等bug,所以不推薦設(shè)置大于1。
知曉了這些參數(shù)的作用之后,我們就可以創(chuàng)建出更符合自己需求的上傳部件:
app2.py
import dash import dash_uploader as du import dash_bootstrap_components as dbc import dash_html_components as html app = dash.Dash(__name__) # 配置上傳文件夾 du.configure_upload(app, folder='temp') app.layout = html.Div( dbc.Container( du.Upload( id='uploader', text='點(diǎn)擊或拖動(dòng)文件到此進(jìn)行上傳!', text_completed='已完成上傳文件:', cancel_button=True, pause_button=True, filetypes=['md', 'mp4'], default_style={ 'background-color': '#fafafa', 'font-weight': 'bold' }, upload_id='我的上傳' ) ) ) if __name__ == '__main__': app.run_server(debug=True)
但像前面的例子那樣直接在定義app.layout時(shí)就傳入實(shí)際的du.Upload()部件,會(huì)產(chǎn)生一個(gè)問題——應(yīng)用啟動(dòng)后,任何訪問應(yīng)用的用戶都對(duì)應(yīng)一樣的upload_id,這顯然不是我們期望的,因?yàn)椴煌脩舻纳蟼魑募?huì)混在一起。
因此可以參考下面例子的方式,在每位用戶訪問時(shí)再渲染隨機(jī)id的上傳部件,從而確保唯一性:
app3.py
import dash import dash_uploader as du import dash_bootstrap_components as dbc import dash_html_components as html import uuid app = dash.Dash(__name__) # 配置上傳文件夾 du.configure_upload(app, folder='temp') def render_random_id_uploader(): return du.Upload( id='uploader', text='點(diǎn)擊或拖動(dòng)文件到此進(jìn)行上傳!', text_completed='已完成上傳文件:', cancel_button=True, pause_button=True, filetypes=['md', 'mp4'], default_style={ 'background-color': '#fafafa', 'font-weight': 'bold' }, upload_id=uuid.uuid1() ) def render_layout(): return html.Div( dbc.Container( render_random_id_uploader() ) ) app.layout = render_layout if __name__ == '__main__': app.run_server(debug=True)
可以看到,每次訪問時(shí)由于upload_id不同,因此不同的會(huì)話擁有了不同的子目錄。
2.1.3 配合du.Upload()進(jìn)行回調(diào)
在du.Upload()中額外還有isCompleted與fileNames兩個(gè)屬性,前者用于判斷當(dāng)前文件是否上傳完成,后者則對(duì)應(yīng)此次上傳的文件名稱,參考下面這個(gè)簡(jiǎn)單的例子:
app4.py
import dash import dash_uploader as du import dash_bootstrap_components as dbc import dash_html_components as html from dash.dependencies import Input, Output, State app = dash.Dash(__name__) # 配置上傳文件夾 du.configure_upload(app, folder='temp') app.layout = html.Div( dbc.Container( [ du.Upload(id='uploader'), html.H5('上傳中或還未上傳文件!', id='upload_status') ] ) ) @app.callback( Output('upload_status', 'children'), Input('uploader', 'isCompleted'), State('uploader', 'fileNames') ) def show_upload_status(isCompleted, fileNames): if isCompleted: return '已完成上傳:'+fileNames[0] return dash.no_update if __name__ == '__main__': app.run_server(debug=True, port=8051)
2.2 配合flask進(jìn)行文件下載
相較于文件上傳,在Dash中進(jìn)行文件的下載就簡(jiǎn)單得多,因?yàn)槲覀兛梢耘浜蟜lask的send_from_directory以及html.A()部件來為指定的服務(wù)器端文件創(chuàng)建下載鏈接,譬如下面的簡(jiǎn)單示例就打通了文件的上傳與下載:
app5.py
from flask import send_from_directory import dash import dash_uploader as du import dash_html_components as html import dash_bootstrap_components as dbc from dash.dependencies import Input, Output import os app = dash.Dash(__name__) du.configure_upload(app, 'temp', use_upload_id=False) app.layout = html.Div( dbc.Container( [ du.Upload(id='upload'), html.Div( id='download-files' ) ] ) ) @app.server.route('/download/<file>') def download(file): return send_from_directory('temp', file) @app.callback( Output('download-files', 'children'), Input('upload', 'isCompleted') ) def render_download_url(isCompleted): if isCompleted: return html.Ul( [ html.Li(html.A(f'/{file}', href=f'/download/{file}', target='_blank')) for file in os.listdir('temp') ] ) return dash.no_update if __name__ == '__main__': app.run_server(debug=True)
3 用Dash編寫簡(jiǎn)易個(gè)人網(wǎng)盤應(yīng)用
在學(xué)習(xí)了今天的案例之后,我們就掌握了如何在Dash中開發(fā)文件上傳及下載功能,下面我們按照慣例,結(jié)合今天的主要內(nèi)容,來編寫一個(gè)實(shí)際的案例;
今天我們要編寫的是一個(gè)簡(jiǎn)單的個(gè)人網(wǎng)盤應(yīng)用,我們可以通過瀏覽器訪問它,進(jìn)行文件的上傳、下載以及刪除:
import dash import dash_bootstrap_components as dbc import dash_html_components as html from dash.dependencies import Input, Output, State import dash_uploader as du import os from flask import send_from_directory import time app = dash.Dash(__name__, suppress_callback_exceptions=True) du.configure_upload(app, 'NetDisk', use_upload_id=False) app.layout = html.Div( dbc.Container( [ html.H3('簡(jiǎn)易的個(gè)人云盤應(yīng)用'), html.Hr(), html.P('文件上傳區(qū):'), du.Upload(id='upload', text='點(diǎn)擊或拖動(dòng)文件到此進(jìn)行上傳!', text_completed='已完成上傳文件:', max_files=1000), html.Hr(), dbc.Row( [ dbc.Button('刪除選中的文件', id='delete-btn', outline=True), dbc.Button('打包下載選中的文件', id='download-btn', outline=True) ] ), html.Hr(), dbc.Spinner( dbc.Checklist( id='file-list-check' ) ), html.A(id='download-url', target='_blank') ] ) ) @app.server.route('/download/<file>') def download(file): return send_from_directory('NetDisk', file) @app.callback( [Output('file-list-check', 'options'), Output('download-url', 'children'), Output('download-url', 'href')], [Input('upload', 'isCompleted'), Input('delete-btn', 'n_clicks'), Input('download-btn', 'n_clicks')], State('file-list-check', 'value') ) def render_file_list(isCompleted, delete_n_clicks, download_n_clicks, check_value): # 獲取上下文信息 ctx = dash.callback_context if ctx.triggered[0]['prop_id'] == 'delete-btn.n_clicks': for file in check_value: try: os.remove(os.path.join('NetDisk', file)) except FileNotFoundError: pass if ctx.triggered[0]['prop_id'] == 'download-btn.n_clicks': import zipfile with zipfile.ZipFile('NetDisk/打包下載.zip', 'w') as zipobj: for file in check_value: try: zipobj.write(os.path.join('NetDisk', file)) except FileNotFoundError: pass return [ {'label': file, 'value': file} for file in os.listdir('NetDisk') if file != '打包下載.zip' ], '打包下載鏈接', '/download/打包下載.zip' time.sleep(2) return [ {'label': file, 'value': file} for file in os.listdir('NetDisk') if file != '打包下載.zip' ], '', '' if __name__ == '__main__': app.run_server(debug=True)
以上就是90行Python代碼開發(fā)個(gè)人云盤應(yīng)用的詳細(xì)內(nèi)容,更多關(guān)于python 開發(fā)個(gè)人云盤的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
python機(jī)器學(xué)習(xí)GCN圖卷積神經(jīng)網(wǎng)絡(luò)原理解析
這篇文章主要為大家介紹了GCN圖卷積神經(jīng)網(wǎng)絡(luò)原理及代碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05NDArray 與 numpy.ndarray 互相轉(zhuǎn)換方式
這篇文章主要介紹了NDArray 與 numpy.ndarray 互相轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05Python操作數(shù)據(jù)庫之?dāng)?shù)據(jù)庫編程接口
這篇文章主要介紹了Python操作數(shù)據(jù)庫之?dāng)?shù)據(jù)庫編程接口,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-06-06python 兩種方法修改文件的創(chuàng)建時(shí)間、修改時(shí)間、訪問時(shí)間
這篇文章主要介紹了python 如何修改文件的創(chuàng)建時(shí)間、修改時(shí)間、訪問時(shí)間的兩種方法,幫助大家更好的利用python處理文件,感興趣的朋友可以了解下2020-09-09Python操作SQLite數(shù)據(jù)庫的方法詳解【導(dǎo)入,創(chuàng)建,游標(biāo),增刪改查等】
這篇文章主要介紹了Python操作SQLite數(shù)據(jù)庫的方法,簡(jiǎn)單說明了sqlite數(shù)據(jù)庫的相關(guān)概念,并結(jié)合實(shí)例形式較為詳細(xì)的分析了Python針對(duì)sqlite數(shù)據(jù)庫的導(dǎo)入,創(chuàng)建,游標(biāo),增刪改查等操作技巧,需要的朋友可以參考下2017-07-07Python強(qiáng)化練習(xí)之Tensorflow2 opp算法實(shí)現(xiàn)月球登陸器
在面向?qū)ο蟪霈F(xiàn)之前,我們采用的開發(fā)方法都是面向過程的編程(OPP)。面向過程的編程中最常用的一個(gè)分析方法是“功能分解”。我們會(huì)把用戶需求先分解成模塊,然后把模塊分解成大的功能,再把大的功能分解成小的功能,整個(gè)需求就是按照這樣的方式,最終分解成一個(gè)一個(gè)的函數(shù)2021-10-10