Coverage for circular_deps / resolvers / python.py: 54%
54 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
1from __future__ import annotations
3import os
5from circular_deps.resolvers.base import PathResolver
8class PythonPathResolver(PathResolver):
9 def resolve(self, module, current_file, root_dir):
10 if module.startswith("."):
11 return self._resolve_relative(module, current_file, root_dir)
12 else:
13 return self._resolve_absolute(module, current_file, root_dir)
15 def _resolve_relative(self, module, current_file, root_dir):
16 current_dir = os.path.dirname(current_file)
18 if module.startswith(".."):
19 parts = []
20 i = 0
21 while i < len(module) and module[i] == ".":
22 parts.append("..")
23 i += 1
24 if parts:
25 module_suffix = module[len(parts) :].strip(".")
26 target_dir = os.path.abspath(os.path.join(current_dir, *parts))
27 if module_suffix:
28 target_path = os.path.join(
29 target_dir, module_suffix.replace(".", os.sep)
30 )
31 else:
32 target_path = target_dir
33 else:
34 target_path = os.path.abspath(
35 os.path.join(current_dir, module.lstrip("."))
36 )
37 else:
38 module_name = module.lstrip(".").strip(".")
39 if module_name:
40 target_path = os.path.abspath(
41 os.path.join(current_dir, module_name.replace(".", os.sep))
42 )
43 else:
44 return None
46 return self._find_py_file(target_path, root_dir)
48 def _resolve_absolute(self, module, current_file, root_dir):
49 module_path = module.replace(".", os.sep)
50 current_dir = os.path.dirname(current_file)
52 for search_path in [current_dir, root_dir]:
53 target_path = os.path.join(search_path, module_path)
54 resolved = self._find_py_file(target_path, root_dir)
55 if resolved:
56 return resolved
58 return None
60 def _find_py_file(self, target_path, root_dir):
61 if os.path.isfile(target_path) and target_path.endswith(".py"):
62 return self._check_under_root(target_path, root_dir)
64 if os.path.isdir(target_path):
65 init_py = os.path.join(target_path, "__init__.py")
66 if os.path.isfile(init_py):
67 return self._check_under_root(init_py, root_dir)
69 py_file = target_path + ".py"
70 if os.path.isfile(py_file):
71 return self._check_under_root(py_file, root_dir)
73 return None
75 def _check_under_root(self, path, root_dir):
76 abs_path = os.path.abspath(path)
77 abs_root = os.path.abspath(root_dir)
78 if abs_path.startswith(abs_root):
79 return abs_path
80 return None