Skip to content

Commit c41a2d5

Browse files
authored
feat(project): add project root discovery utilities
This PR centralizes project root discovery by introducing a dedicated utility module with multiple fallback strategies, replacing ad-hoc path calculations throughout the codebase. The changes improve reliability and maintainability by providing a consistent way to locate the project root directory. * Added get_project_root() utility function with caching and multiple discovery strategies * Refactored environment variable loading to use centralized project root discovery * Introduced custom ProjectRootError exception for clearer error handling
1 parent f12d4e4 commit c41a2d5

3 files changed

Lines changed: 80 additions & 12 deletions

File tree

src/tracklistify/cli.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
# Local/package imports
1515
from .utils.logger import get_logger, set_logger
16+
from .utils.project import get_project_root
1617

1718
# Get the logger for this module
1819
logger = get_logger(__name__)
@@ -145,8 +146,8 @@ def cli() -> None:
145146
logger.info("Starting CLI")
146147

147148
# Load environment variables first
148-
env_path = Path(__file__).parent.parent / ".env"
149-
load_environment_variables(env_path)
149+
env_file = get_project_root() / ".env"
150+
load_environment_variables(env_file)
150151

151152
try:
152153
exit_code = asyncio.run(main(args))

src/tracklistify/core/run.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
1-
#!/usr/bin/env python3
1+
"""Main entry point for Tracklistify."""
22

33
import asyncio
44
import signal
55
import sys
6-
from pathlib import Path
6+
7+
from tracklistify.utils.project import get_project_root
78

89
# Global variables for cleanup
910
_cleanup_tasks = set()
1011

1112

1213
def setup_environment():
1314
"""Setup the Python path and environment variables."""
14-
# Add the parent directory to Python path so tracklistify can be imported
15-
current_dir = Path(__file__).parent.absolute()
16-
sys.path.append(str(current_dir))
17-
18-
# Load environment variables from .env file if it exists
19-
env_file = current_dir / ".env"
20-
if not env_file.exists() and (current_dir / ".env.example").exists():
15+
env_file = get_project_root() / ".env"
16+
if not env_file.exists() and (get_project_root() / ".env.example").exists():
2117
print("Creating .env from .env.example...")
22-
with open(current_dir / ".env.example") as f:
18+
with open(get_project_root() / ".env.example") as f:
2319
with open(env_file, "w") as env:
2420
env.write(f.read())
2521
print("Please edit .env with your credentials")

src/tracklistify/utils/project.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""Project root discovery utilities."""
2+
3+
import os
4+
from functools import lru_cache
5+
from importlib.metadata import PackageNotFoundError, distribution
6+
from pathlib import Path
7+
8+
from .logger import get_logger
9+
10+
logger = get_logger(__name__)
11+
12+
13+
class ProjectRootError(Exception):
14+
"""Raised when project root cannot be determined."""
15+
16+
@lru_cache(maxsize=1)
17+
def get_project_root() -> Path:
18+
"""Get project root directory with multiple fallback strategies.
19+
20+
The result is cached for performance as project root doesn't change during runtime.
21+
22+
Returns:
23+
Path: Absolute path to the project root directory
24+
25+
Raises:
26+
ProjectRootError: If project root cannot be determined reliably
27+
"""
28+
29+
project_root_env = os.getenv("TRACKLISTIFY_PROJECT_ROOT")
30+
31+
if project_root_env:
32+
try:
33+
project_root = Path(project_root_env).resolve()
34+
if project_root.exists():
35+
return project_root
36+
except (OSError, ValueError):
37+
pass
38+
39+
current_file = Path(__file__).resolve()
40+
41+
for parent in [current_file] + list(current_file.parents):
42+
pyproject_path = parent / "pyproject.toml"
43+
if pyproject_path.exists():
44+
return parent
45+
46+
try:
47+
dist = distribution("tracklistify")
48+
package_path = Path(dist.locate_file(""))
49+
50+
if (package_path / "pyproject.toml").exists():
51+
return package_path
52+
53+
for parent in package_path.parents:
54+
pyproject_path = parent / "pyproject.toml"
55+
if pyproject_path.exists():
56+
return parent
57+
58+
except (PackageNotFoundError, AttributeError, OSError):
59+
pass
60+
61+
raise ProjectRootError(
62+
"Unable to determine project root. Please set TRACKLISTIFY_PROJECT_ROOT "
63+
"environment variable or ensure pyproject.toml exists in the project root."
64+
)
65+
66+
67+
def clear_project_root_cache() -> None:
68+
"""Clear the cached project root."""
69+
70+
get_project_root.cache_clear()
71+
logger.debug("Cleared project root cache")

0 commit comments

Comments
 (0)