0

I want to write a minimal FastAPI static file server launched from a script that allows you to specify the directory to share on the command line. Following the example in the FastAPI documentation, I wrote this.

import uvicorn
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

server = FastAPI()

if __name__ == "__main__":
    import sys

    directory = sys.argv[1]
    server.mount("/static", StaticFiles(directory=directory), name="static")
    uvicorn.run(app="my_package:server")

If I run this with the argument /my/directory where this directory contains file.txt I expect that I'd be able to download file.txt at the URL http://localhost:8000/static/file.txt, but this returns an HTTP 404.

How do I write this minimal static file server script?

W.P. McNeill
  • 16,336
  • 12
  • 75
  • 111
  • Is /my/directory actually at root? Or are you trying to mount my/directory instead, referring to a directory alongside the script? – luk2302 Nov 23 '22 at 22:10
  • Root. /my/directory is a path in the file system. "ls /my/directory" returns file.txt. I have read/write permissions for this directory. – W.P. McNeill Nov 23 '22 at 22:12
  • When uvicorn starts its worker with the module you've given it, it will no longer be running `__main__` (since it imports the module you've given it) - so your setup doesn't run when uvicorn _actually_ loads the module (and there is no `sys.argv` when uvicorn starts up, since it _loads the module_ and that isn't invoked through the shell) – MatsLindh Nov 23 '22 at 22:24

2 Answers2

1

The assumption I made about sys.argv not being available when uvicorn loads your module is wrong, so it should work as you expect by moving your static setup outside of the __main__ guard:

import uvicorn
import sys
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

server = FastAPI()
directory = sys.argv[1]
server.mount("/static", StaticFiles(directory=directory), name="static")

if __name__ == "__main__":
    uvicorn.run(app="my_package:server")
MatsLindh
  • 49,529
  • 4
  • 53
  • 84
  • This gives the the same HTTP 404 error as my original script. (I also tried hard-coding directory instead of reading it from sys.argv and got the same result.) – W.P. McNeill Nov 23 '22 at 23:14
  • @W.P.McNeill It works perfectly fine here. `python foo2.py "d:\foo\bar"` with `foo.txt` in that directory and then accessing `http://localhost:8000/static/foo.txt` gives the content of the file back. – MatsLindh Nov 24 '22 at 10:05
  • Never mind. Sorry. I was making a mistake on my machine. The solution here is correct. As long as the server.mount line is not inside the __name__ == "__main__" section everthing works. – W.P. McNeill Nov 24 '22 at 16:51
0

When you call uvicorn.run(app="my_package:server"), it actually starts a separate process where my_package is imported. Therefore, everything inside if __name__ == "__main__": will not be run in the uvicorn process, so your directory will never be mounted.

One possible solution would be getting the directory from an environment variable, which is set from a small bash script:

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

server = FastAPI()

directory = os.getenv("DIRECTORY")
server.mount("/static", StaticFiles(directory=directory), name="static")

start.sh:

#!/usr/bin/env bash
DIRECTORY=$1 uvicorn mypackage:server
M.O.
  • 1,712
  • 1
  • 6
  • 18
  • Oh, I didn't realize you could use `sys.argv` would be available, in that case @MatsLindh's solution is cleaner – M.O. Nov 23 '22 at 22:43