Debian conveniently distribute JavaScript libraries, and expects packaged software to use them rather than embedding their own copy.
Here is a convenient custom StaticFileHandler for Tornado that looks for the Debian-distributed versions of JavaScript libraries, and falls back to the vendored versions if they are not found:
from tornado import web
import pathlib
class StaticFileHandler(web.StaticFileHandler):
"""
StaticFileHandler that allows overriding paths in the static directory with
system provided versions
"""
SYSTEM_ASSET_PATH = pathlib.Path("/usr/share/javascript")
@classmethod
def get_absolute_path(self, root, path):
path = pathlib.PurePath(path)
if not path.parts:
return super().get_absolute_path(root, path)
system_dir = self.SYSTEM_ASSET_PATH.joinpath(path.parts[0])
if system_dir.is_dir():
# If that asset directory exists in the system, look for things in
# there
return self.SYSTEM_ASSET_PATH.joinpath(path)
else:
# Else go ahead with the default static dir
return super().get_absolute_path(root, path)
def validate_absolute_path(self, root, absolute_path):
"""
Rewrite of tornado's validate_absolute_path not to raise an error for
paths in /usr/share/javascript/
"""
root = pathlib.Path(root)
absolute_path = pathlib.Path(absolute_path)
is_system_root = absolute_path.parts[:len(self.SYSTEM_ASSET_PATH.parts)] == self.SYSTEM_ASSET_PATH.parts
is_static_root = absolute_path.parts[:len(root.parts)] == root.parts
if not is_system_root and not is_static_root:
raise web.HTTPError(403, "%s is not in root static directory or system assets path",
self.path)
if absolute_path.is_dir() and self.default_filename is not None:
# need to look at the request.path here for when path is empty
# but there is some prefix to the path that was already
# trimmed by the routing
if not self.request.path.endswith("/"):
self.redirect(self.request.path + "/", permanent=True)
return
absolute_path = absolute_path.joinpath(self.default_filename)
if not absolute_path.exists():
raise web.HTTPError(404)
if not absolute_path.is_file():
raise web.HTTPError(403, "%s is not a file", self.path)
return str(absolute_path)
This is how to use it:
class DebianApplication(tornado.web.Application):
def __init__(self, *args, **settings):
from .static import StaticFileHandler
settings.setdefault("static_handler_class", StaticFileHandler)
super().__init__(*args, **settings)
And from HTML it's simply a matter of matching the first path component to what
is used by Debian's packages under /usr/share/javascript
:
<link rel="stylesheet" href="{{static_url('bootstrap4/css/bootstrap.min.css')}}">
<script src="{{static_url('jquery/jquery.min.js')}}"></script>
<script src="{{static_url('popper.js/umd/popper.min.js')}}"></script>
<script src="{{static_url('bootstrap4/js/bootstrap.min.js')}}"></script>
I find it quite convenient: this way I can start writing prototype code without worrying about fetching javascript libraries to bundle.
I only need to start worrying about it if I need to deploy outside of Debian,
or to old stable versions of Debian that don't contain the required JavaScript
dependencies. In that case, I just cp -r
from a working
/usr/share/javascript
into Tornado's static directory, and I'm done.