Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How does the way the app is run affect whether book code works? #8

Closed
gitgithan opened this issue Aug 9, 2022 · 4 comments
Closed

Comments

@gitgithan
Copy link

gitgithan commented Aug 9, 2022

Since chapter3_first_endpoint_01, we have been instructed to run uvicorn as uvicorn chapter3_first_endpoint_01:app, implicitly meaning we should go inside the chapter3 folder first.

Later I got stuck at chapter3_custom_response_04 because I ran it as uvicorn chapter3_custom_response_04:app, which starts uvicorn proper, but upon httpie query gives Internal Server Error.

RuntimeError: File at path /Users/hanqi/code/Building-Data-Science-Applications-with-FastAPI/chapter3/assets/cat.jpg does not exist.

To investigate:

print(__file__)
print(path.dirname(__file__))
print(path.dirname(path.dirname(__file__)))

Output:

/Users/hanqi/code/Building-Data-Science-Applications-with-FastAPI/chapter3/./chapter3_custom_response_04.py
/Users/hanqi/code/Building-Data-Science-Applications-with-FastAPI/chapter3/.
/Users/hanqi/code/Building-Data-Science-Applications-with-FastAPI/chapter3

At that moment I just hacked my way through by wrapping another path.dirname after observing the folder structure to see assets folder is on parallel level as chapter folders.
It is pretty impossible for the average reader to know that it has to be ran from outside chapter3, with uvicorn chapter3.chapter3_custom_response_04:app for the httpie GET to work, after being accustomed to all previous exercises being run from inside chapter3.

Same 3 prints when run from outside chapter3:

/Users/hanqi/code/Building-Data-Science-Applications-with-FastAPI/./chapter3/chapter3_custom_response_04.py
/Users/hanqi/code/Building-Data-Science-Applications-with-FastAPI/./chapter3
/Users/hanqi/code/Building-Data-Science-Applications-with-FastAPI/.

Question 1/2
Why is there a dot added somewhere along the path? (It caused me to have to wrap a 3rd path.dirname to move up 1 more level past the dot when I ran it wrongly).
Is that vscode/fastapi/uvicorn's behaviour? What's the logic to where in the path, or when it is added? (making a simple test.py with print(__file__) inside chapter3 folder and python -m test doesn't show this extra dot)

Only at chapter6/sqlalchemy did i realize what i did wrong in chapter3.
Again I ran uvicorn sqlalchemy.app:app (inside chapter6 folder) and got

  File "/Users/hanqi/.pyenv/versions/3.8.12/envs/lewagon/lib/python3.8/site-packages/databases/core.py", line 10, in <module>
    from sqlalchemy import text
ImportError: cannot import name 'text' from 'sqlalchemy' (/Users/hanqi/code/Building-Data-Science-Applications-with-FastAPI/chapter6/./sqlalchemy/__init__.py)

Then i saw from chapter6.sqlalchemy.models import in app.py and realized I must run it outside chapter6 with
uvicorn chapter6.sqlalchemy.app:app.

Question 2/2
From the error I see databases/core.py is trying to import something from sqlalchemy.
Is this a case of clashing names, and the sqlalchemy folder overwrote the sqlalchemy library because the folder came first in import path?
If this is true, why does running uvicorn chapter6.sqlalchemy.app:app outside chapter 6 correctly look for the sqlachemy library since the folder should still come first in import path? Is it because python does not recursively search into the Building-Data-Science-Applications-with-FastAPI folder, so doesn't know there is a sqlachemy folder there too and thus no clash this time?

P.S I tested uvicorn sqlalchemy_relationship.app:app inside chapter6 hoping there's no clash but get same error

@frankie567
Copy link
Collaborator

Well, that may be something I overlooked because of the cat picture which is located at the root folder, not in chapter3. If you are in the root folder, you can run the example like this without any issue:

uvicorn chapter3.chapter3_custom_response_04:app

About dotted path

The path chapter3.chapter3_custom_response_04 is what we usually call the dot notation in Python. It's a way to tell Python the path of a module. In Python, a module is any valid Python file from which we can import functions or variables: chapter3_custom_response_04.py is a module.

Modules are contained in packages. Packages are special directories for Python. To make Python treat a directory as a package, you need to create an __init__.py file (which can be totally empty) at the root of this directory. chapter3 has an __init__.py file, so it's a package.

This is why we can tell Python to look for chapter3.chapter3_custom_response_04: chapter3_custom_response_04 is a valid module inside chapter3 package.

The tricky __file__ variable

We use the __file__ variable to know the path of the current module. This variable adds a . at some point to tell us where we run the script from. Here how we are getting the root directory from the example:

root_directory = path.dirname(path.dirname(__file__))

Basically, we take the parent of the parent of the current file. If we run it from the root directory, __file__ has the following value:

/Users/fvoron/Development/Building-Data-Science-Applications-with-FastAPI/./chapter3/chapter3_custom_response_04.py

So taking the parent twice returns us the right path:

/Users/fvoron/Development/Building-Data-Science-Applications-with-FastAPI/.

However, if we run it from chapter3, __file__ has the following value:

/Users/fvoron/Development/Building-Data-Science-Applications-with-FastAPI/chapter3/./chapter3_custom_response_04.py

If we take the parent twice here, we get chapter3 (because the parent of . is chapter3...), which is not the path we want:

/Users/fvoron/Development/Building-Data-Science-Applications-with-FastAPI/chapter3

How to solve this?

A more robust solution would probably have been to make Python resolve the path so we don't have this problem. Doing this solves the problem whether we run the script from the root or chapter3:

from os import path
from pathlib import Path

from fastapi import FastAPI
from fastapi.responses import FileResponse

app = FastAPI()


@app.get("/cat")
async def get_cat():
    root_directory = Path(__file__).parent.parent
    picture_path = path.join(root_directory, "assets", "cat.jpg")
    return FileResponse(picture_path)

Basically, Path is a more high-level library to help us manage path. When we pass it a string path, it'll resolve it completely so it'll discard the .. Again, we get the parent twice.

About chapter6 and name clashing

This is a more or less related problem of where we run the script from. If we run it from the root directory like this, no problem:

uvicorn chapter6.sqlalchemy.app:app

We get the module app inside the package sqlalchemy, which is itself inside the chapter6 package.

Problems arise if we want to run it from chapter6. My clumsiness here was to call the package sqlalchemy which is in conflict with the library sqlalchemy. In this context, Python will always choose the "closest" package, i.e. our package, and ignore the library. This is why we get cannot import name 'text' from 'sqlalchemy'.

Why does it work when running from the root folder? Simply because there is no sqlalchemy package in the root folder. If we had one, we would get the same problem. Once again, the "bad" thing I did was to name my package exactly like the library.

Hope it clarifies things!

@rajat-packt
Copy link

@gitgithan let me know if we can close this issue now.
@frankie567 do tell me if there is something regarding this exchange that needs to be added to the README if it can benefit our other reader!

@gitgithan
Copy link
Author

@rajat-packt Yep we can close this, thanks so much to frankie567 for being so willing to share knowledge

@JamesCHub
Copy link

@gitgithan let me know if we can close this issue now.
@frankie567 do tell me if there is something regarding this exchange that needs to be added to the README if it can benefit our other reader!

I got tripped up with the sqlalchemy directory name issue myself, and Google searches did not find this closed issue (I just happened to find this when looking to file a new issue on the matter) - would suggest an update to the README or any other online resource to suggest using a different name for that directory (and a note that it is never a good idea to name anything in your code the same as a reserved word, package/subpackage/module name, etc)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

4 participants