5
\$\begingroup\$

I'm currently still in the early stages of learning Python and Jupyter notebooks, and I wanted to ask if the following code for returning absolute paths relative to the directory of a script (.py) or Jupyter notebook (.ipynb) file could be improved, particularly on the test which checks whether the absolute_path() function is called in a .py or .ipynb file. ruff tells me not to use bare except, but I don't know how to deal with the warning.

# $HOME/
#    └─ project/
#          ├─ data/
#          └─ src/
#              └─ project/
#                    ├─ project.ipynb
#                    └─ project.py
# A script with path
# $HOME/project/src/project/project.py
# or a cell inside a Jupyter notebook with path
# $HOME/project/src/project/project.ipynb
from pathlib import Path

import ipynbname
from IPython import get_ipython


def absolute_path(relative_path: str | None = None) -> Path:
    try:
        get_ipython()
        file_path = Path(ipynbname.path())

    except:
        file_path = Path(__file__).resolve()

    file_dir_path = file_path.parent
    path = Path(
        f"{file_dir_path}"
        if relative_path is None
        else f"{file_dir_path}/{relative_path}"
    ).resolve()
    return path


root_dir_path = absolute_path("../..")
data_dir_path = absolute_path("../../data")
src_dir_path = absolute_path("..")
file_dir_path = absolute_path()

print(f"{root_dir_path = }")
print(f"{data_dir_path = }")
print(f"{src_dir_path = }")
print(f"{file_dir_path = }")
New contributor
Frances is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
\$\endgroup\$
3
  • 1
    \$\begingroup\$ Welcome! Could you explain why you posted the same code in the 2nd and 3rd code blocks? Aside from the comment line at the top of the code, they are the same. \$\endgroup\$ Commented 20 hours ago
  • \$\begingroup\$ @toolic Sorry, I forgot to specify that the 2nd and 3rd code blocks are indeed redundant. Through the comment line at the top, I was emphasizing the need for the same code block to work both in a regular .py file and in a cell inside a .ipynb file. I can edit out the 3rd code block if it obfuscates the question, should I do that? \$\endgroup\$ Commented 5 hours ago
  • \$\begingroup\$ @toolic Specifically, should I delete the 3rd code block and edit the top-comment line of the 2nd code block into # A script with path $HOME/project/src/project/project.py or a cell inside a Jupyter notebook with path $HOME/project/src/project/project.ipynb? \$\endgroup\$ Commented 5 hours ago

4 Answers 4

3
\$\begingroup\$

I've read the code of the ipynbname module and you could replace the bare except clause by

except FileNotFoundError:

This is publicly documented in the docstring of ipynbname.path().

Also you could remove the call to get_ipython() as you don't do anything with its return value, so I don't think it serves any purpose. (actually ipynbname.path() internally calls get_ipython() but it does use the return value).

\$\endgroup\$
3
\$\begingroup\$

One thing the other answers haven't addressed is this:

    file_dir_path = file_path.parent
    path = Path(
        f"{file_dir_path}"
        if relative_path is None
        else f"{file_dir_path}/{relative_path}"
    ).resolve()
    return path

Repeatedly making Paths into strings and back again isn't very Pythonic. Instead, consider using something like:

    path = file_path.parent
    if relative_path is not None:
        path /= relative_path
    return path.resolve()

-- or indeed, remove the relative_path altogether and use the / operator outside of the call to absolute_path, like Kate suggests.

\$\endgroup\$
1
  • \$\begingroup\$ I think if relative_path: ... suffices. \$\endgroup\$ Commented 14 hours ago
1
\$\begingroup\$

try/except

ruff tells me not to use bare except, but I don't know how to deal with the warning

You should investigate what type of error you expect to get from the code following the try statement. If the documentation for get_ipython mentions different types of common error situations, or if you have encountered specific errors when using it, then you would want to trap those error types. The reason for the recommendation from ruff is to avoid trapping all errors. Doing so may make the code difficult to debug.

Documentation

The PEP 8 style guide recommends adding docstrings for functions and at the top of the code to summarize its purpose.

"""
Return absolute paths relative to the directory of a script (.py)
or Jupyter notebook (.ipynb) file.
Also state why this code is useful.
"""
\$\endgroup\$
1
\$\begingroup\$

First of all, congrats for using ruff. If you are using code versioning (and you should), then consider using ruff as a pre-commit hook (reference).

Now there is inconsistency in what the function absolute_path suggests and what it actually does. The name absolute_path implies that it will indeed return an absolute path. But it takes a relative_path parameter. So the end result is something else. Apples and oranges. Just return the absolute_path. I would be even more precise and name that function get_application_path or something along these lines, so there is no ambiguity.

In plain Python, I would use something like this to get the directory of the running script:

current_dir = os.path.dirname(os.path.realpath(__file__))

But I am not familiar with IPython and there might be a better way. Leaning heavily on a specific Python build makes the script potentially less portable though.

Better paths

Since you are using pathlib, you can embrace the slash notation (pathlib reference) eg:

from pathlib import Path

data_dir_path = absolute_path() / "." / "." / "data"

Storage

I suspect you are looking to store data in your app, then the importlib.resources lib should interest you. Especially if you are looking to embed resources (icons, files etc) used by your app.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ you write data_dir_path = absolute_path() / "." / "." / "data" as a replacement for data_dir_path = absolute_path("../../data") - this is wrong and also not the best way to utilize pathlib. Better would be absolute_path().parents[1] / "data" or absolute_path().parent.parent / "data", since pathlib natively includes ways to get the parent directories. \$\endgroup\$ Commented 15 hours ago

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.