BokehServer doesnot recognize directories/files on disk

(Bokeh3.4.1) BokehServer is unable to load /save files to disk folder -saveData located at the servers root .

BrowserError : POST http://localhost:5006/saveData 404 (Not Found)

How to get the server to recognize and read from files stored in folders on disk.

CODE:

# bokeh serve --show C:\Users\User_0\Documents\Code\Python\NseScraping\Others\bokeh\dataTable_save_2a.py  --port 5006


import os
import pandas as pd
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, DataTable, TableColumn, Button, CustomJS
from bokeh.server.server import Server
from tornado.ioloop import IOLoop
from tornado.web import RequestHandler
import json

csv_file_path = 'my_data.csv'

if os.path.exists(csv_file_path):
    df = pd.read_csv(csv_file_path)
else:
    # Sample data if the file doesn't exist
    df = pd.DataFrame({
        'names': ['Csv failed to load from disk'],
    })

source = ColumnDataSource(df)

columns = [
    TableColumn(field="names", title="Name"),
    TableColumn(field="ages", title="Age"),
]

data_table = DataTable(source=source, columns=columns, editable=True, width=400, height=280)

button = Button(label="Save as CSV", button_type="success")

button_callback = CustomJS(args=dict(source=source), code="""
    const data = source.data;
    const json = JSON.stringify(data);
    const xhr = new XMLHttpRequest();
    xhr.open("POST", "saveData", true);
    xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
    xhr.onload = function () {
        if (xhr.status === 200) {
            console.log("Save path:", JSON.parse(xhr.responseText).path);
        } else {
            console.error("Error:", JSON.parse(xhr.responseText).message);
        }
    };
    xhr.send(json);
""")
button.js_on_click(button_callback)

layout = column(data_table, button)

curdoc().add_root(layout)

# Tornado RequestHandler to save data as CSV
class SaveCSVHandler(RequestHandler):
    def post(self):
        try:
            data = json.loads(self.request.body.decode('utf-8'))
            df = pd.DataFrame(data)
            df.to_csv(csv_file_path, index=False)
            self.write({'status': 'success', 'path': csv_file_path})
            print(f"Data saved to {csv_file_path}")
        except Exception as e:
            self.write({'status': 'error', 'message': str(e)})
            print(f"Error saving data: {str(e)}")

def modify_doc(doc):
    doc.add_root(layout)

def start_server():
    extra_patterns = [('saveData', SaveCSVHandler)]
    server = Server({'/': modify_doc}, io_loop=IOLoop.current(), extra_patterns=extra_patterns, allow_websocket_origin=["localhost:5006"])
    server.start()
    server.io_loop.add_callback(server.show, "/")
    server.io_loop.start()

if __name__ == '__main__':
    start_server()

@srt111 Sometimes running actual code can quickly uncover an issue. I am happy to try things out directly, but you’ll need to edit your post to make the code immediately copy-pasteable first (i.e. remove all the junk quote characters at the start of each line).

@Bryan Code Edited …the ‘preformatted text’ option had added all that junk.

@srt111 Before I have a chance to run this please up the log level to debug since that will explicitly show all the configured patterns:

2024-06-10 11:59:15,212 Patterns are:
2024-06-10 11:59:15,212   [('/favicon.ico',
2024-06-10 11:59:15,212     <class 'bokeh.server.views.ico_handler.IcoHandler'>,
2024-06-10 11:59:15,212     {'app': <bokeh.server.tornado.BokehTornado object at 0x122e75640>}),
2024-06-10 11:59:15,212    ('/sliders/?',
2024-06-10 11:59:15,212     <class 'bokeh.server.views.doc_handler.DocHandler'>,
2024-06-10 11:59:15,212     {'application_context': <bokeh.server.contexts.ApplicationContext object at 0x1239de330>,
2024-06-10 11:59:15,212      'bokeh_websocket_path': '/sliders/ws'}),
2024-06-10 11:59:15,212    ('/sliders/ws',
2024-06-10 11:59:15,212     <class 'bokeh.server.views.ws.WSHandler'>,
2024-06-10 11:59:15,212     {'application_context': <bokeh.server.contexts.ApplicationContext object at 0x1239de330>,
2024-06-10 11:59:15,212      'bokeh_websocket_path': '/sliders/ws',
2024-06-10 11:59:15,212      'compression_level': None,
2024-06-10 11:59:15,212      'mem_level': None}),
2024-06-10 11:59:15,212    ('/sliders/metadata',
2024-06-10 11:59:15,212     <class 'bokeh.server.views.metadata_handler.MetadataHandler'>,
2024-06-10 11:59:15,212     {'application_context': <bokeh.server.contexts.ApplicationContext object at 0x1239de330>,
2024-06-10 11:59:15,212      'bokeh_websocket_path': '/sliders/ws'}),
2024-06-10 11:59:15,212    ('/sliders/autoload.js',
2024-06-10 11:59:15,212     <class 'bokeh.server.views.autoload_js_handler.AutoloadJsHandler'>,
2024-06-10 11:59:15,212     {'application_context': <bokeh.server.contexts.ApplicationContext object at 0x1239de330>,
2024-06-10 11:59:15,212      'bokeh_websocket_path': '/sliders/ws'}),
2024-06-10 11:59:15,212    ('/sliders/static/(.*)',
2024-06-10 11:59:15,212     <class 'bokeh.server.views.static_handler.StaticHandler'>,
2024-06-10 11:59:15,212     {}),
2024-06-10 11:59:15,212    ('/?',
2024-06-10 11:59:15,212     <class 'bokeh.server.views.root_handler.RootHandler'>,
2024-06-10 11:59:15,212     {'applications': {'/sliders': <bokeh.server.contexts.ApplicationContext object at 0x1239de330>},
2024-06-10 11:59:15,212      'index': None,
2024-06-10 11:59:15,212      'prefix': '',
2024-06-10 11:59:15,212      'use_redirect': True}),
2024-06-10 11:59:15,212    ('/static/extensions/(.*)',
2024-06-10 11:59:15,212     <class 'bokeh.server.views.multi_root_static_handler.MultiRootStaticHandler'>,
2024-06-10 11:59:15,212     {'root': {}}),
2024-06-10 11:59:15,212    ('/static/(.*)',
2024-06-10 11:59:15,212     <class 'bokeh.server.views.static_handler.StaticHandler'>)]
2024-06-10 11:59:15,213 Bokeh app running at: http://localhost:5006/sliders

In a “script” server app (i.e. run with bokeh serve app.py) you could just add the command line argument --log-level=debug but you’ll need to pass something to Server in this kind of situation IIRC, you may need to enable logging generally in the usual Python ways, as well)