Skip to content

Commit

Permalink
Merge pull request #162 from nmpowell/allow-notebooks-in-other-locations
Browse files Browse the repository at this point in the history
Allow notebooks in other locations
  • Loading branch information
pydanny committed Mar 2, 2024
2 parents 3c9b92d + 9449c4a commit 1ed3c2d
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 20 deletions.
1 change: 0 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
Please read the [Development - Contributing](https://dj-notebook.readthedocs.io/en/latest/contributing/) guidelines in the documentation site.

5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pip install dj_notebook

First, find your project's `manage.py` file and open it. Copy whatever is being set to `DJANGO_SETTINGS_MODULE` into your clipboard.

Create an ipython notebook in the same directory as `manage.py`. In VSCode,
Create an ipython notebook in the same directory as `manage.py`, or another directory of your choosing. In VSCode,
simply add a new `.ipynb` file. If using Jupyter Lab, use the `File -> New ->
Notebook` menu option.

Expand All @@ -50,6 +50,9 @@ from dj_notebook import activate

plus = activate()

# If you have created your notebook in a different directory, instead do:
# plus = activate(search_dir="/path/to/your/project")

# If that throws an error, try one of the following:

# DJANGO_SETTINGS_MODULE_VALUE aka "book_store.settings"
Expand Down
9 changes: 7 additions & 2 deletions src/dj_notebook/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@


def activate(
settings: str = None, quiet_load: bool = True, *, dotenv_file: StrPath | None = None
settings: str | None = None,
quiet_load: bool = True,
*,
dotenv_file: StrPath | None = None,
search_dir: StrPath | None = None,
) -> Plus:
with Status(
"Loading dj-notebook...\n Use Plus.print() to see what's been loaded.",
Expand All @@ -31,7 +35,8 @@ def activate(
os.environ["DJANGO_SETTINGS_MODULE"] = settings
else:
source, discovered_settings = find_django_settings_module(
dotenv_file=dotenv_file
dotenv_file=dotenv_file,
search_dir=search_dir,
)
if discovered_settings:
if not quiet_load:
Expand Down
41 changes: 25 additions & 16 deletions src/dj_notebook/config_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,28 +55,34 @@ def is_root(path: Path) -> bool:
# dotenv_file should be rewritten as Optional[StrPath] = None and the return type should be annotated as
# Tuple[str, Optional[str]]
def find_django_settings_module(
*, dotenv_file: StrPath | None = None
*,
dotenv_file: StrPath | None = None,
search_dir: StrPath | None = None,
) -> Tuple[str, str | None]:
"""
Find the name of the first settings module from the environment or the closest `manage.py` file.
Returns: a tuple(source, module name) telling the caller where the module was found and the name of the module.
The optional, keyword-only argument `dotenv_file` will be explicitly loaded prior to searching the environment,
if supplied.
Optional keyword-only arguments:
dotenv_file: Absolute or relative path to .env file, loaded prior to searching the environment.
search_dir: Absolute or relative path to the directory to start searching for a `manage.py` file, used if
`dotenv_file` is `None`.
If both `dotenv_file` and `search_dir` are `None`, the environment variable DJANGO_SETTINGS_MODULE is checked,
and the current working directory (and its parents and immediate subdirectories) is searched for a `manage.py` file.
"""
settings_module = None
# First see if this has either already been set in the environment or put in a .env file that python-dotenv will
# treat that way
settings_module = None
if not dotenv_file:
source = "environment"
settings_module = os.environ.get("DJANGO_SETTINGS_MODULE", None)
if not settings_module:
if dotenv_file:
# load with override=True if the caller has specified a dotenv file explicitly
source = "dotenv"
load_dotenv(dotenv_file, override=bool(dotenv_file))
settings_module = os.environ.get("DJANGO_SETTINGS_MODULE", None)
settings_module = os.environ.get("DJANGO_SETTINGS_MODULE", None)
elif not search_dir:
source = "environment"
settings_module = os.environ.get("DJANGO_SETTINGS_MODULE", None)
# If we get nothing from the environment, look for a `manage.py` script containing a call that sets a default in the
# current working directory or in any parent. This should accommodate the common pattern of
# search directory, the current working directory, or any parent. This should accommodate the common pattern of
# - app1
# - app2
# - project
Expand All @@ -85,9 +91,9 @@ def find_django_settings_module(
# - notebooks
# --> analysis_notebook.ipynb
# - manage.py
search_dir = Path.cwd().resolve()
current_search_dir = Path(search_dir or Path.cwd()).resolve()
while settings_module is None:
manage_py = search_dir / "manage.py"
manage_py = current_search_dir / "manage.py"
if manage_py.is_file():
for call in setdefault_calls(manage_py):
if (
Expand All @@ -96,20 +102,23 @@ def find_django_settings_module(
):
settings_module = call.args[1].value
source = f"{manage_py.resolve().absolute()}"
elif is_root(search_dir):
elif is_root(current_search_dir):
break
else:
search_dir = search_dir.parent.resolve()
current_search_dir = current_search_dir.parent.resolve()
if not settings_module:
# Finally, go one level down into children of the current working directory to see if a `manage.py` with a default
# Finally, go one level down into children of the search directory to see if a `manage.py` with a default
# for `DJANGO_SETTINGS_MODULE` can be found there. This accommodates the common pattern of
# - analysis.ipynb
# - src
# --> manage.py
# --> project
# ----> settings.py
# ...
for p in [Path(subdir) for subdir in os.scandir(Path.cwd())]:
for p in [
Path(subdir)
for subdir in os.scandir(Path(search_dir or Path.cwd()).resolve())
]:
manage_py = p / "manage.py"
if manage_py.is_file():
for call in setdefault_calls(manage_py):
Expand Down
29 changes: 29 additions & 0 deletions tests/test_config_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,32 @@ def test_find_django_settings_module_os_environment():
assert source == "environment"
assert os.environ["DJANGO_SETTINGS_MODULE"] == found
assert found == "something.else"


def test_find_django_settings_module_remote_path():
script_dir_path = Path(os.path.dirname(os.path.realpath(__file__)))
django_project_path = script_dir_path / "django_test_project"
old_cwd = os.getcwd()
# Change to a directory that is not the django project or adjacent.
os.chdir(script_dir_path / "..")
with EnvironmentGuard():
source, found = find_django_settings_module(search_dir=django_project_path)
assert source == str(django_project_path / "manage.py")
assert found == "book_store.settings"
# Change to the parent directory in order to search children
source, found = None, None
with EnvironmentGuard():
source, found = find_django_settings_module(
search_dir=django_project_path / ".."
)
assert source == str(django_project_path / "manage.py")
assert found == "book_store.settings"
# Change to a child directory in order to search parents
source, found = None, None
with EnvironmentGuard():
source, found = find_django_settings_module(
search_dir=django_project_path / "book_store"
)
assert source == str(django_project_path / "manage.py")
assert found == "book_store.settings"
os.chdir(old_cwd)

0 comments on commit 1ed3c2d

Please sign in to comment.