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_join
dumbly 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_directory
tests the safe filename withos.path.isfile
, which works relative to the working directory, to see if the file exists so it can404
if 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 in
files/will just
404`. -
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.
Thx so much!