Say you have a package with this layout:
my_project/
my_app/
files/
im_a_file.txt
__init__.py
run_my_app.py
...and you want to send files from files/. So in a view function in __init__.py or something, you do return send_from_directory('files', 'im_a_file.txt'). It doesn't work. Here's how it breaks:
-
safe_joindumbly concatenates the directory and path (assuming they're safe), so the path returned is still relative, so you getfilename == 'files/im_a_file.txt'. -
send_from_directorytests the safe filename withos.path.isfile, which works relative to the working directory, to see if the file exists so it can404if it doesn't. The test fails, because the working directory isproject, notproject/my_app. A passing filename would bemy_app/files/im_a_file.txt'. Any file path infiles/will just404`. -
Ok, so flask is weird, lets just hack our code to accommodate
send_from_directory's use of the current working directory. We change our line toreturn send_from_directory('my_app/files', 'im_a_file.txt'). It still breaks:
3 a) from safe_join, we now get my_app/files/im_a_file.txt. my_app/files/im_a_file.txt passes the os.path.isfile test, so we don't 404 this time.
3 b) the new safe filename is passed to send_file. In send_file, the path is tested to see if it's an absolute path. It is not, so send_file makes it absolute by prepending current_app.root_path to it, which in our case of a package app is /path/to/my_project/my_app. So our filename is now /path/to/my_project/my_app/my_app/files/im_a_file.txt.
2 c) That path doesn't exist, so later when it's opened, it errors out with IOError.
To re-emphasize, this is an issue with with relative directories used with package apps. Absolute directories work, because current_app.root_path is never prepended. Module apps work because os.getcwd() == current_app.root_path for them.
I hit this exact same issue.