Dash extensions problem downloading PDF file generated as a byte stream - python

I have a Dash application where the user interacts with the app and a PDF will be generated using FPDF. I am trying to use the Dash-extensions package and the download option to forward the PDF to be downloaded but am getting an error.
I have this function to generate the PDF:
def makeReport(info):
return create_pdf(info)
def create_pdf(info):
pdf = CustomPDF(format='letter')
for i, beam in enumerate(info):
pdf.addPage(info[i],i)
data = pdf.output(dest='S').encode('latin-1')
return data
Then this is the download button related code:
#app.callback(Output("download", "data"), [Input("download_btn", "n_clicks")], prevent_initial_call=True)
def sendPDF(n_clicks):
firstName, lastName, mrn = nameParser(patient)
fileName = lastName + ', ' + firstName + ' Cutout Factor Calc.pdf'
return send_bytes(makeReport(info), fileName)
def downloadButton():
return html.Div([html.Hr(),html.Button("Download Report", id="download_btn"), Download(id="download")])
The error I get when I click the button is:

The send_bytes utility function expects as first argument a function that writes bytes to BytesIO object, but you are passing it a byte string, so you need to write a small wrapper function. Here is a small example demonstrating how it can be done,
import dash
import dash_html_components as html
from dash.dependencies import Output, Input
from dash_extensions import Download
from dash_extensions.snippets import send_bytes
from fpdf import FPDF
# Create example app.
app = dash.Dash(prevent_initial_callbacks=True)
app.layout = html.Div([html.Button("Download PDF", id="btn"), Download(id="download")])
def create_pdf(n_nlicks):
pdf = FPDF()
pdf.add_page()
pdf.set_font('Arial', 'B', 16)
pdf.cell(40, 10, f'Hello World! (n_clicks is {n_nlicks})')
return pdf
#app.callback(Output("download", "data"), [Input("btn", "n_clicks")])
def generate_xlsx(n_nlicks):
def write_pdf(bytes_io):
pdf = create_pdf(n_nlicks) # pass argument to PDF creation here
bytes_io.write(pdf.output(dest='S').encode('latin-1'))
return send_bytes(write_pdf, "some_name.pdf")
if __name__ == '__main__':
app.run_server()

Related

ImportError: cannot import name 'config' from 'plotly.io._json'

I am trying to make a basic Dash app, and I got this error. Here is my code.
import dash
import plotly
from dash import dcc
#from dash_core_components.Markdown import Markdown
from dash import html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
import os
import PyPDF2
app = dash.Dash(__name__, meta_tags=[{"name": "viewport", "content": "width=device- width, initial-scale=1"}], external_stylesheets=[dbc.themes.SUPERHERO])
app.title = 'FODS'
server = app.server
app.layout = html.Div([
dcc.Input(
id='input_text',
type= 'text',
placeholder="Enter Unicode Search Phrase".format('text'),
),
html.Div(id = 'output-area')
])
#app.callback(
Output('output-area', 'children'),
Input('input_text', 'value'),
)
def return_pages(input_text):
locations = []
answers = 'Please refer '
for file in os.listdir('./Solutions'):
path = './Solutions/' + file
pdfFileObj = open(path, 'rb')
pdfReader = PyPDF2.PdfFileReader(pdfFileObj, strict = False)
for i in range(pdfReader.numPages):
pageObj = pdfReader.getPage(i)
text = str(pageObj.extractText())
if input_text in text:
locations.append([file, i+1])
answers += file + ', at page ' + str(i + 1) + ', '
if len(locations) != 0:
return answers + ' for your answers'
else:
return 'Sorry, your query fetched no answers'
if __name__ == '__main__':
app.run_server(debug=True)
Screenshot of the error on local development server
I have tried updating the libraries and restarting the venv. Please advise on what can be done. There is a slight time crunch on this project.
Thanks in advance.

I want to create Python Dash app to watch a directory over ftp , extract data from the file and plot it in real time

I am working on a project on which I have to monitor a ftp directory for any new files which appear , once a new file appears I want to extract data from and plot the results in real time so far I have sorted out the ftp function that monitors the directory , for testing purpose I am using my phone as a test ftp server and I have created a function which uploads a new file every n seconds :
from ftplib import FTP
from time import sleep
import os
ftp = FTP()
ftp.connect('192.168.1.109', 2221)
ftp.login('android', 'android')
ftp.cwd('/test_folder')
folder = 'C:\\Users\\QC\\Desktop\\sample_files_to_upload'
for file in os.listdir(folder):
fp = open(folder + file,'rb')
ftp.storbinary('STOR %s' % os.path.basename(file), fp, 1024)
sleep(2)
fp.close()
sleep(2)
print("File {} uploaded sucessfully".format(file))
I also have a function which watches the directory for any new files which appear in the directory and as soon as the file appears it copies it on the local machine performs data extraction prints the output and removes the file:
from ftplib import FTP
from time import sleep
import time
import os
def monitor_ftp():
ftp = FTP()
ftp.connect('192.168.1.109', 2221', 21)
ftp.login('android', 'android)
ftp.cwd('/test_folder')
print("Connection Established {}".format(ftp.getwelcome()))
direct = 'C:\\Users\\QC\\Desktop\\local_temp_directory\\'
old_files = ['1']
#print(old_files)
while True:
try:
new_files = ftp.nlst()
#print(new_files)
#print(new_files)
if len(old_files) != 0 and new_files != old_files:
changes = [i for i in new_files if i not in old_files]
#
# print(changes)
for x in changes:
filename = str(direct + x)
localfile = open(filename, 'wb')
ftp.retrbinary('RETR'+' ' + x , localfile.write, 1024)
localfile.close()
xcorr = extract_function(filename)
print("updating data ***************************************************")
print("found new file---> {}".format(str(filename).split('\\')[-1]))
print("Calculating cross-correlation")
print("*****************************************************************")
print(" ")
sleep(3)
os.remove(filename)
a = time.perf_counter()
if time.perf_counter() > a + 20:
print("Done Waiting")
break
old_files = new_files
except KeyboardInterrupt:
ftp.quit()
I have also created a basic test_dash app :
import dash
import plotly.graph_objects as go
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import dash_bootstrap_components as dbc
from app import app
app = dash.Dash(__name__)
app.layout = html.Div(
[
dcc.Graph(id = 'live-graph', animate = True),
dcc.Interval(
id = 'graph-update',
interval = 10000,
n_intervals = 0
),
html.Div(dbc.Row([
dbc.Col(html.H2(id='check_update_div', children='check ' ))
]) )
]
)
#app.callback(
Output('check_update_div', 'children'),
[ Input('graph-update', 'n_intervals') ]
)
def update_graph_scatter(n):
print('one loop done ')
a = monitor_ftp()
return ("this is X {}".format(a))
I want to tie everything together and create a dash app which monitors the ftp directory and plots the result of data extract , currently I don't have a graph in my app, I have just put a div which I want to update with the file name for every new file that appears in the folder , but I am not sure how to combine these functions together. Can you please help.
Edit:
I have got a working example which shows that ftp inside the callback function prevents the function from returning anything , if ftp is outside the return function then callback returns and updates the div every n_intervals
import dash
from dash.dependencies import Output, Input
import dash_core_components as dcc
import dash_html_components as html
import plotly
import random
import plotly.graph_objs as go
from collections import deque
from ftplib import FTP
from time import sleep
#ftp otside the function and function works fine
#Deactivae this part and activate ftp inside the function , and function
#stops returning anything and div is not updated every n_interval
ftp = FTP()
ftp.connect('10.199.44.240', 21)
ftp.login('display')
ftp.cwd('/home/display/test_qc')
print("Connection Established {}".format(ftp.getwelcome()))
direct = 'C:\\Users\\QC\\Desktop\\Gunlink_local\\'
app = dash.Dash(__name__)
app.layout = html.Div([
html.Div([
dcc.Interval(
id = 'graph-update',
interval = 10000,
n_intervals = 0
)
]),
html.Div([
html.H1(id='print_value', children="Output Value"),
])
])
#app.callback(
Output('print_value', 'children'),
[ Input('graph-update', 'n_intervals') ]
)
def update_value(n):
# ftp inside the function prevents the function from returning anything
# Div is not updated every n_interval
'''
ftp = FTP()
ftp.connect('10.199.44.240', 21)
ftp.login('display')
ftp.cwd('/home/display/test_qc')
print("Connection Established {}".format(ftp.getwelcome()))
a = ftp.nlst()
'''
a = ftp.nlst()
sleep(2)
print('one loop done ')
return ("this is X {}".format(a))
if __name__ == '__main__':
app.run_server()
You need the interval component.
Dash cannot do constant updates, like running that while loop nonstop, but you can have the interval set to fire a callback every X seconds. Using that callback, you can check the FTP server on that schedule for updates.
Check out this page of the docs for some good examples of using it to create a live-updating page.

Unable to access dataframe while uploading form plotly-dash app

I am new to python and plotly-dash.
I am trying to use "Hidden Div" to store a data frame as suggested in dash tutorial 5.
But I am not able to process the uploaded file.
import base64
import io
import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
import dash_table
import pandas as pd
#global_df = pd.read_csv('...')
app = dash.Dash(__name__)
app.layout = html.Div([
dcc.Graph(id='graph'),
html.Table(id='table'),
dcc.Upload(
id='datatable-upload',
children=html.Div(['Drag and Drop or ',html.A('Select Files')]),
),
# Hidden div inside the app that stores the intermediate value
html.Div(id='intermediate-value', style={'display': 'none'})
])
def parse_contents(contents, filename):
content_type, content_string = contents.split(',') #line 28
decoded = base64.b64decode(content_string)
if 'csv' in filename:
# Assume that the user uploaded a CSV file
return pd.read_csv(
io.StringIO(decoded.decode('utf-8')))
elif 'xls' in filename:
# Assume that the user uploaded an excel file
return pd.read_excel(io.BytesIO(decoded))
elif 'xlsx' in filename:
# Assume that the user uploaded an excel file
return pd.read_excel(io.BytesIO(decoded))
#app.callback(Output('intermediate-value', 'children'),
[Input('datatable-upload', 'contents')],
[State('datatable-upload', 'filename')])
def update_output(contents, filename):
# some expensive clean data step
cleaned_df = parse_contents(contents, filename)
# more generally, this line would be
# json.dumps(cleaned_df)
return cleaned_df.to_json(date_format='iso', orient='split')
#app.callback(Output('graph', 'figure'), [Input('intermediate-value', 'children')])
def update_graph(jsonified_cleaned_data):
# more generally, this line would be
# json.loads(jsonified_cleaned_data)
dff = pd.read_json(jsonified_cleaned_data, orient='split')
figure = create_figure(dff)
return figure
#app.callback(Output('table', 'children'), [Input('intermediate-value', 'children')])
def update_table(jsonified_cleaned_data):
dff = pd.read_json(jsonified_cleaned_data, orient='split')
table = create_table(dff)
return table
if __name__ == '__main__':
app.run_server(port=8050, host='0.0.0.0')
I am getting the following error while running the code:
File "ipython-input-12-4bd6fe1b7399", line 28, in parse_contents
content_type, content_string = contents.split(',')
AttributeError: 'NoneType' object has no attribute 'split'
The callback is likely running on initialization with empty values. You can prevent this by adding something like this at the top of your callback:
if contents is None:
raise dash.exceptions.PreventUpdate

Redirecting Python's console output to Dash

How can I redirect/show the console output (including outputs I print inside the program) so it would appear inside a Dash app (On the screen the user sees)?
I didn't find a way for Dash to read the console output directly, so I used a workaround using a text file.
Here is a sample code which prints the last 20 lines of the console output to an Iframe (as to keep the line breaks, which do not show in other text/div components):
import dash_core_components as dcc
import dash_html_components as html
import dash
import sys
f = open('out.txt', 'w')
f.close()
app = dash.Dash()
app.layout = html.Div([
dcc.Interval(id='interval1', interval=1 * 1000,
n_intervals=0),
dcc.Interval(id='interval2', interval=5 * 1000,
n_intervals=0),
html.H1(id='div-out', children=''),
html.Iframe(id='console-out',srcDoc='',style={'width':
'100%','height':400})
])
#app.callback(dash.dependencies.Output('div-out',
'children'),
[dash.dependencies.Input('interval1', 'n_intervals')])
def update_interval(n):
orig_stdout = sys.stdout
f = open('out.txt', 'a')
sys.stdout = f
print 'Intervals Passed: ' + str(n)
sys.stdout = orig_stdout
f.close()
return 'Intervals Passed: ' + str(n)
#app.callback(dash.dependencies.Output('console-out',
'srcDoc'),
[dash.dependencies.Input('interval2', 'n_intervals')])
def update_output(n):
file = open('out.txt', 'r')
data=''
lines = file.readlines()
if lines.__len__()<=20:
last_lines=lines
else:
last_lines = lines[-20:]
for line in last_lines:
data=data+line + '<BR>'
file.close()
return data
app.run_server(debug=False, port=8050)
Create a custom LoggerHandler, and use dcc.Interval for refreshing.
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import dash
import logging
class DashLoggerHandler(logging.StreamHandler):
def __init__(self):
logging.StreamHandler.__init__(self)
self.queue = []
def emit(self, record):
msg = self.format(record)
self.queue.append(msg)
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
dashLoggerHandler = DashLoggerHandler()
logger.addHandler(dashLoggerHandler)
app = dash.Dash()
app.layout = html.Div([
dcc.Interval(id='interval1', interval=5 * 1000, n_intervals=0),
html.H1(id='div-out', children='Log'),
html.Iframe(id='console-out',srcDoc='',style={'width': '100%','height':400})
])
#app.callback(
Output('console-out', 'srcDoc'),
Input('interval1', 'n_intervals'))
def update_output(n):
return ('\n'.join(dashLoggerHandler.queue)).replace('\n', '<BR>')
app.run_server(debug=False, port=8050)
One possible way to do this is supply it as input to Dash Core Component Text Area's value property. This should work if its a string.

Uploading files using Browse Button in Jupyter and Using/Saving them

I came across this snippet for uploading files in Jupyter however I don't know how to save this file on the machine that executes the code or how to show the first 5 lines of the uploaded file. Basically I am looking for proper commands for accessing the file after it has been uploaded:
import io
from IPython.display import display
import fileupload
def _upload():
_upload_widget = fileupload.FileUploadWidget()
def _cb(change):
decoded = io.StringIO(change['owner'].data.decode('utf-8'))
filename = change['owner'].filename
print('Uploaded `{}` ({:.2f} kB)'.format(
filename, len(decoded.read()) / 2 **10))
_upload_widget.observe(_cb, names='data')
display(_upload_widget)
_upload()
_cb is called when the upload finishes. As described in the comment above, you can write to a file there, or store it in a variable. For example:
from IPython.display import display
import fileupload
uploader = fileupload.FileUploadWidget()
def _handle_upload(change):
w = change['owner']
with open(w.filename, 'wb') as f:
f.write(w.data)
print('Uploaded `{}` ({:.2f} kB)'.format(
w.filename, len(w.data) / 2**10))
uploader.observe(_handle_upload, names='data')
display(uploader)
After the upload has finished, you can access the filename as:
uploader.filename
I am working on ML with Jupyter notebook, and I was looking for a solution to select the local files containing the datasets by browsing amongst the local file system. Although, the question here refers more to uploading than selecting a file. I am putting here a snippet that I found here because when I was looking for a solution for my particular case, the result of the search took me several times to here.
import os
import ipywidgets as widgets
class FileBrowser(object):
def __init__(self):
self.path = os.getcwd()
self._update_files()
def _update_files(self):
self.files = list()
self.dirs = list()
if(os.path.isdir(self.path)):
for f in os.listdir(self.path):
ff = os.path.join(self.path, f)
if os.path.isdir(ff):
self.dirs.append(f)
else:
self.files.append(f)
def widget(self):
box = widgets.VBox()
self._update(box)
return box
def _update(self, box):
def on_click(b):
if b.description == '..':
self.path = os.path.split(self.path)[0]
else:
self.path = os.path.join(self.path, b.description)
self._update_files()
self._update(box)
buttons = []
if self.files:
button = widgets.Button(description='..', background_color='#d0d0ff')
button.on_click(on_click)
buttons.append(button)
for f in self.dirs:
button = widgets.Button(description=f, background_color='#d0d0ff')
button.on_click(on_click)
buttons.append(button)
for f in self.files:
button = widgets.Button(description=f)
button.on_click(on_click)
buttons.append(button)
box.children = tuple([widgets.HTML("<h2>%s</h2>" % (self.path,))] + buttons)
And to use it:
f = FileBrowser()
f.widget()
# <interact with widget, select a path>
# in a separate cell:
f.path # returns the selected path
4 years later this remains an interesting question, though Fileupload has slightly changed and belongs to ipywidgets....
Here is some demo that shows how to get the file/files after the button click and reset the button to get more files....
from ipywidgets import FileUpload
def on_upload_change(change):
if not change.new:
return
up = change.owner
for filename,data in up.value.items():
print(f'writing [{filename}] to ./')
with open(filename, 'wb') as f:
f.write(data['content'])
up.value.clear()
up._counter = 0
upload_btn = FileUpload()
upload_btn.observe(on_upload_change, names='_counter')
upload_btn
And here is a "debug" version that shows what is going on / why things work...
from ipywidgets import FileUpload
def on_upload_change(change):
if change.new==0:
print ('cleared')
return
up = change.owner
print (type(up.value))
for filename,data in up.value.items():
print('==========================================================================================')
print(filename)
for k,v in data['metadata'].items():
print(f' -{k:13}:[{v}]')
print(f' -content len :[{len(data["content"])}]')
print('==========================================================================================')
up.value.clear()
up._counter = 0
upload_btn = FileUpload()
upload_btn.observe(on_upload_change, names='_counter')
upload_btn
I stumbled into this thread ~2 years late. For those still confused about how to work with the fileupload widget I have built off of the excellent answer posted by minrk with some other usage examples below.
from IPython.display import display
import fileupload
uploader = fileupload.FileUploadWidget()
def _handle_upload(change):
w = change['owner']
with open(w.filename, 'wb') as f:
f.write(w.data)
print('Uploaded `{}` ({:.2f} kB)'.format(
w.filename, len(w.data) / 2**10))
uploader.observe(_handle_upload, names='data')
display(uploader)
From the widget documentation:
class FileUploadWidget(ipywidgets.DOMWidget):
'''File Upload Widget.
This widget provides file upload using `FileReader`.
'''
_view_name = traitlets.Unicode('FileUploadView').tag(sync=True)
_view_module = traitlets.Unicode('fileupload').tag(sync=True)
label = traitlets.Unicode(help='Label on button.').tag(sync=True)
filename = traitlets.Unicode(help='Filename of `data`.').tag(sync=True)
data_base64 = traitlets.Unicode(help='File content, base64 encoded.'
).tag(sync=True)
data = traitlets.Bytes(help='File content.')
def __init__(self, label="Browse", *args, **kwargs):
super(FileUploadWidget, self).__init__(*args, **kwargs)
self._dom_classes += ('widget_item', 'btn-group')
self.label = label
def _data_base64_changed(self, *args):
self.data = base64.b64decode(self.data_base64.split(',', 1)[1])
Get the data in bytestring format:
uploader.data
Get the data in a regular utf-8 string:
datastr= str(uploader.data,'utf-8')
Make a new pandas dataframe from the utf-8 string (e.g. from a .csv input):
import pandas as pd
from io import StringIO
datatbl = StringIO(datastr)
newdf = pd.read_table(datatbl,sep=',',index_col=None)
You have to enable the file upload option in your code, to enable the browse button to appear in your notebook.
Run the following
!jupyter nbextension enable fileupload --user --py

Categories

Resources