Failing to serve static files with bottle

I am using Python 3.9.2 and Bottle 0.12.19 on Debian.

my web dir layout is:

testapp.py
static/index.html

Here is a run:

$ python3 -m bottle -blocalhost:7070 testapp
Bottle v0.12.19 server starting up (using WSGIRefServer())...
Listening on http://localhost:7070/
Hit Ctrl-C to quit.

127.0.0.1 - - [15/Nov/2023 08:39:10] "GET /_test HTTP/1.1" 200 68
127.0.0.1 - - [15/Nov/2023 08:39:17] "GET /index.html HTTP/1.1" 404 740

As you can see _test works fine, but the static index.html file cannot be found.

Here is my testapp.py:

#!/usr/bin/env python3

import sys

import bottle
import bottle.ext.sqlite
import whitenoise

app = bottle.default_app()
plugin = bottle.ext.sqlite.Plugin(dbfile='./mydata.db')
app.install(plugin)

@app.get('/_test')
def _test():
    return f'Python {sys.version}'

app = whitenoise.WhiteNoise(app) 
app.add_files('./static', prefix='')

I haven’t done much web programming so it could just be some silly oversight… can anyone help please?

I have only a basic understanding of web frameworks, and haven’t heard of Whitenoise. But when I check the documentation, I see:

root (str) – Absolute path to a directory of static files to be served

prefix (str) – If set, the URL prefix under which the files will be served. Trailing slashes are automatically added.

So my guesses are:

  • Maybe the CWD isn’t what you expect? Or else there’s some reason it just really can’t use a relative path.

  • Maybe it’s trying to serve //index.html, and the prefix should instead be left as the default None instead of an empty string?

It also looks like you should be able to pass a index_file keyword argument to the WhiteNoise constructor to make GET / work.

I did wonder about the paths so changed the code to this (but it gave the same errors):

#!/usr/bin/env python3

import os
import sys

import bottle
import bottle.ext.sqlite
import whitenoise


ROOT = os.path.abspath(os.path.dirname(__file__))

app = bottle.default_app()
plugin = bottle.ext.sqlite.Plugin(dbfile=os.path.join(ROOT, 'mydata.db'))
app.install(plugin)

@app.get('/_test')
def _test():
    return f'Python {sys.version}'

app = whitenoise.WhiteNoise(app, root=os.path.join(ROOT, 'static'))

I finally solved it.

#!/usr/bin/env python3

import os
import sys

import bottle
import bottle.ext.sqlite

ROOT = os.path.abspath(os.path.dirname(__file__))

app = bottle.default_app()
plugin = bottle.ext.sqlite.Plugin(dbfile=os.path.join(ROOT, 'mydata.db'))
app.install(plugin)

@app.get('/_test')
def _test():
    return f'Python {sys.version}'

@app.route('/<filename>')
def server_static(filename):
    return bottle.static_file(filename, root=os.path.join(ROOT, 'static'))