|
16 | 16 |
|
17 | 17 | import os |
18 | 18 | from pathlib import Path |
19 | | -from typing import Optional |
| 19 | +from typing import Iterator, Optional |
20 | 20 |
|
21 | 21 | from datacustomcode.file.base import BaseDataAccessLayer |
22 | 22 |
|
@@ -66,54 +66,48 @@ def find_file_path(self, file_name: str) -> Path: |
66 | 66 | file_name: The name of the file to open |
67 | 67 |
|
68 | 68 | Returns: |
69 | | - A file path |
| 69 | + A file path that exists |
70 | 70 |
|
71 | 71 | Raises: |
72 | 72 | FileNotFoundError: If the file cannot be found |
73 | 73 | """ |
74 | 74 | if not file_name: |
75 | 75 | raise ValueError("file_name cannot be empty") |
76 | 76 |
|
77 | | - file_path = self._resolve_file_path(file_name) |
| 77 | + tried: list[Path] = [] |
| 78 | + for candidate in self._candidate_paths(file_name): |
| 79 | + tried.append(candidate) |
| 80 | + if candidate.exists(): |
| 81 | + return candidate |
78 | 82 |
|
79 | | - if not file_path.exists(): |
80 | | - raise FileNotFoundError( |
81 | | - f"File '{file_name}' not found in any search location" |
82 | | - ) |
| 83 | + raise FileNotFoundError( |
| 84 | + f"File '{file_name}' not found in any search location. " |
| 85 | + f"Tried: {[str(p) for p in tried]}" |
| 86 | + ) |
83 | 87 |
|
84 | | - return file_path |
85 | | - |
86 | | - def _resolve_file_path(self, file_name: str) -> Path: |
87 | | - """Resolve the full path to a file. |
| 88 | + def _candidate_paths(self, file_name: str) -> Iterator[Path]: |
| 89 | + """Yield candidate paths for ``file_name`` in resolution order. |
88 | 90 |
|
89 | 91 | Args: |
90 | 92 | file_name: The name of the file to resolve |
91 | 93 |
|
92 | 94 | Returns: |
93 | | - The full path to the file |
| 95 | + An iterator of candidate paths |
94 | 96 | """ |
95 | | - # First check if environment variable is set |
| 97 | + # 1. $LIBRARY_PATH/<file_folder>/<file_name>, then $LIBRARY_PATH/<file_name> |
96 | 98 | env_path = os.getenv(self.DEFAULT_ENV_VAR) |
97 | 99 | if env_path: |
98 | | - file_path = Path(env_path) / file_name |
99 | | - if file_path.exists(): |
100 | | - return file_path |
| 100 | + yield Path(env_path) / self.file_folder / file_name |
| 101 | + yield Path(env_path) / file_name |
101 | 102 |
|
102 | | - # First try the default code package location |
| 103 | + # 2. <code_package>/<file_folder>/<file_name> relative to cwd |
103 | 104 | if self._code_package_exists(): |
104 | | - file_path = self._get_code_package_file_path(file_name) |
105 | | - if file_path.exists(): |
106 | | - return file_path |
| 105 | + yield self._get_code_package_file_path(file_name) |
107 | 106 |
|
108 | | - # Fall back to config.json-based location |
| 107 | + # 3. <config_dir>/<file_folder>/<file_name> via config.json discovery |
109 | 108 | config_path = self._find_config_file() |
110 | | - if config_path: |
111 | | - file_path = self._get_config_based_file_path(file_name, config_path) |
112 | | - if file_path.exists(): |
113 | | - return file_path |
114 | | - |
115 | | - # Return the file name as a Path if not found in any location |
116 | | - return Path(file_name) |
| 109 | + if config_path is not None: |
| 110 | + yield self._get_config_based_file_path(file_name, config_path) |
117 | 111 |
|
118 | 112 | def _code_package_exists(self) -> bool: |
119 | 113 | """Check if the default code package directory exists. |
@@ -146,15 +140,18 @@ def _find_config_file(self) -> Optional[Path]: |
146 | 140 | def _get_config_based_file_path(self, file_name: str, config_path: Path) -> Path: |
147 | 141 | """Get the file path relative to the config file location. |
148 | 142 |
|
| 143 | + Anchors on the directory containing the discovered ``config.json`` so a |
| 144 | + package found by walking up from cwd resolves files relative to its own |
| 145 | + root, not the caller's cwd. |
| 146 | +
|
149 | 147 | Args: |
150 | 148 | file_name: The name of the file |
151 | 149 | config_path: The path to the config file |
152 | 150 |
|
153 | 151 | Returns: |
154 | 152 | The full path to the file |
155 | 153 | """ |
156 | | - relative_path = f"{self.file_folder}/{file_name}" |
157 | | - return Path(relative_path) |
| 154 | + return config_path.parent / self.file_folder / file_name |
158 | 155 |
|
159 | 156 | def _find_file_in_tree(self, filename: str, search_path: Path) -> Optional[Path]: |
160 | 157 | """Find a file within a directory tree. |
|
0 commit comments