Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/nooa/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@


def find_project_root() -> Path:
"""Walk up from this file to find the project root (where pyproject.toml lives).
"""Find the nearest project root by walking up from the working directory.

Falls back to ``Path.cwd()`` if no ``pyproject.toml`` is found (e.g. when
the package is installed into site-packages rather than run from source).
Include the working directory itself and fall back to it when no
``pyproject.toml`` is found, independently of the library's installation path.
"""
current = Path(__file__).resolve()
for parent in current.parents:
current = Path.cwd().resolve()
for parent in (current, *current.parents):
if (parent / "pyproject.toml").exists():
return parent
return Path.cwd()
return current


def get_user_dir(*parts: str) -> Path:
Expand Down
43 changes: 42 additions & 1 deletion tests/test_paths.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,52 @@
# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

from nooa.paths import get_user_dir
import pytest

from nooa.paths import find_project_root, get_project_dir, get_user_dir


def test_xdg_user_dir_uses_nooa_name(tmp_path, monkeypatch):
"""User-level settings retain their XDG-based nooa directory."""
monkeypatch.delenv("NEMO_OO_USER_DIR", raising=False)
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path))

assert get_user_dir() == tmp_path / "nooa"


@pytest.mark.parametrize("nested", [False, True])
def test_project_root_starts_at_working_directory(tmp_path, monkeypatch, nested):
"""Project-local paths belong to the caller's project, including from subdirectories."""
project = tmp_path / "project"
project.mkdir()
(project / "pyproject.toml").touch()
cwd = project / "src" if nested else project
cwd.mkdir(exist_ok=True)
monkeypatch.chdir(cwd)
monkeypatch.delenv("NEMO_OO_PROJECT_DIR", raising=False)

assert find_project_root() == project
assert get_project_dir("sessions") == project / ".nooa" / "sessions"


def test_project_root_uses_nearest_project(tmp_path, monkeypatch):
"""A nested project's marker takes precedence over an ancestor project's marker."""
(tmp_path / "pyproject.toml").touch()
child = tmp_path / "child"
child.mkdir()
(child / "pyproject.toml").touch()
monkeypatch.chdir(child)
assert find_project_root() == child


def test_project_root_without_marker_falls_back_to_cwd(tmp_path, monkeypatch):
"""Without a project marker, local state stays beneath the working directory."""
monkeypatch.chdir(tmp_path)
assert find_project_root() == tmp_path


def test_project_dir_honors_override(tmp_path, monkeypatch):
"""An explicit project-directory override still wins over automatic discovery."""
override = tmp_path / "custom-nooa"
monkeypatch.setenv("NEMO_OO_PROJECT_DIR", str(override))
assert get_project_dir("settings.yaml") == override / "settings.yaml"