Skip to content

Commit 9fc0d9e

Browse files
authored
refactor(config): streamline project root handling and update paths (#35)
This pull request refactors project root path discovery and configuration handling to centralize logic, simplify usage, and improve consistency across the codebase. The main changes include replacing the old get_project_root utility with a new get_root function, updating configuration defaults to use absolute paths relative to the project root, and reorganizing settings in the configuration classes. Project root path discovery and usage: Introduced a new get_root function in src/tracklistify/config/paths.py for project root discovery, replacing the previous logic in src/tracklistify/utils/project.py. Updated all references to project root throughout the codebase (src/tracklistify/cli.py, src/tracklistify/core/run.py, config modules) to use get_root instead of get_project_root. Configuration defaults and environment variable handling: Changed default directory paths in BaseConfig to be absolute paths resolved from the project root using get_root, ensuring consistent placement of logs, cache, temp, and output files. Improved environment variable loading for path fields: relative paths from environment variables are now resolved relative to the project root. Configuration settings reorganization: Reorganized and updated fields in TrackIdentificationConfig, including changes to default values, grouping settings by theme (provider, caching, rate limiting, output, downloader), and adding new fields for flexibility. These changes make project configuration more robust and easier to maintain by centralizing root path logic and improving clarity of config settings.
1 parent c41a2d5 commit 9fc0d9e

7 files changed

Lines changed: 94 additions & 97 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,12 @@ cython_debug/
164164
.ruff_cache/
165165
.vscode/
166166
.DS_Store
167+
copilot*
167168
poetry.lock
168169

169170
# Project-specific
170171
.tracklistify/
171172
examples/
172173
dev/
173174
docs/temp/
175+
coverage.json

src/tracklistify/cli.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@
88

99
from dotenv import load_dotenv
1010

11-
from .config import ConfigError, get_config
11+
from .config import ConfigError, get_config, get_root
1212
from .core import ApplicationError, AsyncApp
1313

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

1817
# Get the logger for this module
1918
logger = get_logger(__name__)
@@ -146,8 +145,8 @@ def cli() -> None:
146145
logger.info("Starting CLI")
147146

148147
# Load environment variables first
149-
env_file = get_project_root() / ".env"
150-
load_environment_variables(env_file)
148+
env_path = get_root() / ".env"
149+
load_environment_variables(env_path)
151150

152151
try:
153152
exit_code = asyncio.run(main(args))

src/tracklistify/config/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44
# Local imports
55
from .base import BaseConfig, TrackIdentificationConfig
66
from .factory import ConfigError, clear_config, get_config
7+
from .paths import clear_root, get_root
78

89
__all__ = [
910
"BaseConfig",
1011
"TrackIdentificationConfig",
1112
"get_config",
1213
"clear_config",
1314
"ConfigError",
15+
"get_root",
16+
"clear_root",
1417
]

src/tracklistify/config/base.py

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,23 @@
77
from typing import List
88

99
# Local imports
10+
from .paths import get_root
1011
from .validation import ConfigValidator, PathRequirement, PathRule
1112

1213

1314
@dataclass
1415
class BaseConfig:
1516
"""Base configuration class."""
1617

17-
output_dir: Path = field(default=Path(".tracklistify/output"))
18-
cache_dir: Path = field(default=Path(".tracklistify/cache"))
19-
temp_dir: Path = field(default=Path(".tracklistify/temp"))
20-
log_dir: Path = field(default=Path(".tracklistify/log"))
18+
project_root = get_root()
19+
20+
# Directories
21+
log_dir: Path = field(default=project_root / ".tracklistify/log")
22+
temp_dir: Path = field(default=project_root / ".tracklistify/temp")
23+
cache_dir: Path = field(default=project_root / ".tracklistify/cache")
24+
output_dir: Path = field(default=project_root / ".tracklistify/output")
25+
26+
# Log settings
2127
verbose: bool = field(default=False)
2228
debug: bool = field(default=False)
2329

@@ -61,8 +67,12 @@ def _load_from_env(self) -> None:
6167
# Handle boolean values
6268
value = env_value.lower() in ("true", "1", "yes", "on")
6369
elif field_type == Path:
64-
# Handle paths
65-
value = Path(os.path.expanduser(env_value))
70+
# Handle paths - resolve relative paths relative to project root
71+
path = Path(os.path.expanduser(env_value))
72+
if not path.is_absolute():
73+
# Relative path - resolve relative to project root
74+
path = get_root() / path
75+
value = path
6676
elif field_type == List[str]:
6777
# Handle string lists (comma-separated)
6878
value = [s.strip() for s in env_value.split(",") if s.strip()]
@@ -101,36 +111,52 @@ class TrackIdentificationConfig(BaseConfig):
101111
# Base config fields are inherited
102112

103113
# Track identification specific fields
104-
segment_length: int = field(default=30)
105-
overlap_duration: int = field(default=10)
114+
segment_length: int = field(default=60)
106115
min_confidence: float = field(default=0.5)
107-
time_threshold: float = field(default=60.0)
116+
time_threshold: float = field(default=30.0)
108117
max_duplicates: int = field(default=2)
118+
overlap_duration: int = field(default=10)
119+
overlap_strategy: str = field(default="weighted")
120+
min_segment_length: int = field(default=10)
121+
122+
# Provider settings
109123
primary_provider: str = field(default="shazam")
110-
fallback_enabled: bool = field(default=True)
124+
fallback_enabled: bool = field(default=False)
111125
fallback_providers: List[str] = field(default_factory=list)
112126

113-
# Global rate limiting settings
114-
max_requests_per_minute: int = field(default=25)
115-
max_concurrent_requests: int = field(default=2)
116-
127+
# Caching settings
117128
cache_enabled: bool = field(default=True)
118-
cache_ttl: int = field(default=86400)
119-
cache_max_size: int = field(default=1000000)
129+
cache_ttl: int = field(default=3600)
130+
cache_max_size: int = field(default=1000)
120131
cache_storage_format: str = field(default="json")
121132
cache_compression_enabled: bool = field(default=True)
122133
cache_compression_level: int = field(default=6)
123134
cache_cleanup_enabled: bool = field(default=True)
124135
cache_cleanup_interval: int = field(default=3600)
125-
cache_max_age: int = field(default=604800)
136+
cache_max_age: int = field(default=86400)
126137
cache_min_free_space: int = field(default=104857600)
138+
139+
# Rate limiting settings
140+
max_requests_per_minute: int = field(default=25)
141+
max_concurrent_requests: int = field(default=2)
142+
143+
# ACRCloud settings
127144
acrcloud_max_rpm: int = field(default=300)
128145
acrcloud_max_concurrent: int = field(default=10)
146+
147+
# Shazam settings
129148
shazam_max_rpm: int = field(default=25)
130149
shazam_max_concurrent: int = field(default=1)
131150
shazam_cooldown_seconds: float = field(default=2.25)
151+
152+
# Output formats
132153
output_format: str = field(default="json")
133154

155+
# Downloader settings
156+
download_quality: str = field(default="192")
157+
download_format: str = field(default="mp3")
158+
download_max_retries: int = field(default=3)
159+
134160
def __post_init__(self):
135161
"""Initialize configuration after dataclass creation."""
136162
# Load environment variables first

src/tracklistify/config/paths.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""Project path discovery utilities."""
2+
3+
import os
4+
from functools import lru_cache
5+
from pathlib import Path
6+
7+
8+
@lru_cache(maxsize=1)
9+
def get_root() -> Path:
10+
"""Get project root directory for loading .env files and resolving config paths.
11+
12+
Simple approach:
13+
1. Check environment variable TRACKLISTIFY_PROJECT_ROOT
14+
2. Walk up from current file until we find pyproject.toml
15+
3. Fallback to current working directory
16+
17+
Returns:
18+
Path: Project root directory
19+
"""
20+
# Environment variable override
21+
if root_env := os.getenv("TRACKLISTIFY_PROJECT_ROOT"):
22+
root_path = Path(root_env).resolve()
23+
if root_path.exists():
24+
return root_path
25+
26+
# Walk up from this file to find pyproject.toml
27+
current = Path(__file__).resolve()
28+
for parent in current.parents:
29+
if (parent / "pyproject.toml").exists():
30+
return parent
31+
32+
# Fallback to current working directory
33+
return Path.cwd()
34+
35+
36+
def clear_root() -> None:
37+
"""Clear the project root cache."""
38+
get_root.cache_clear()

src/tracklistify/core/run.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@
44
import signal
55
import sys
66

7-
from tracklistify.utils.project import get_project_root
7+
from tracklistify.config import get_root
88

99
# Global variables for cleanup
1010
_cleanup_tasks = set()
1111

1212

1313
def setup_environment():
1414
"""Setup the Python path and environment variables."""
15-
env_file = get_project_root() / ".env"
16-
if not env_file.exists() and (get_project_root() / ".env.example").exists():
15+
env_path = get_root() / ".env"
16+
if not env_path.exists() and (get_root() / ".env.example").exists():
1717
print("Creating .env from .env.example...")
18-
with open(get_project_root() / ".env.example") as f:
19-
with open(env_file, "w") as env:
18+
with open(get_root() / ".env.example") as f:
19+
with open(env_path, "w") as env:
2020
env.write(f.read())
2121
print("Please edit .env with your credentials")
2222
sys.exit(1)

src/tracklistify/utils/project.py

Lines changed: 0 additions & 71 deletions
This file was deleted.

0 commit comments

Comments
 (0)