graphlens-python 0.2.1__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: graphlens-python
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: Python language adapter for graphlens
5
5
  Requires-Dist: graphlens
6
6
  Requires-Dist: tree-sitter>=0.24
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "graphlens-python"
3
- version = "0.2.1"
3
+ version = "0.3.0"
4
4
  description = "Python language adapter for graphlens"
5
5
  requires-python = ">=3.13"
6
6
  dependencies = ["graphlens", "tree-sitter>=0.24", "tree-sitter-python>=0.23"]
@@ -14,6 +14,7 @@ from graphlens import (
14
14
  RelationKind,
15
15
  )
16
16
  from graphlens.utils import make_node_id
17
+ from graphlens.utils.roots import filter_nested_root_files
17
18
 
18
19
  from graphlens_python._deps import (
19
20
  PYTHON_DEFAULT_DEP_PARSERS,
@@ -95,8 +96,14 @@ class PythonAdapter(LanguageAdapter):
95
96
  self._dep_parsers,
96
97
  )
97
98
  else:
98
- for py_root in find_python_roots(project_root):
99
+ py_roots = find_python_roots(project_root)
100
+ for py_root in py_roots:
99
101
  root_files = self.collect_files(py_root)
102
+ root_files = filter_nested_root_files(
103
+ root_files,
104
+ py_root,
105
+ py_roots,
106
+ )
100
107
  _analyze_root(
101
108
  graph,
102
109
  project_root,
@@ -6,6 +6,8 @@ import configparser
6
6
  import tomllib
7
7
  from typing import TYPE_CHECKING
8
8
 
9
+ from graphlens.utils import collect_marker_roots
10
+
9
11
  if TYPE_CHECKING:
10
12
  from pathlib import Path
11
13
 
@@ -47,38 +49,21 @@ def find_python_roots(search_root: Path) -> list[Path]:
47
49
  """
48
50
  Find the actual Python project roots within search_root.
49
51
 
50
- If search_root itself has Python markers, returns ``[search_root]``.
51
- Otherwise walks subdirectories for marker files and returns their parent
52
- directories one per distinct Python sub-project. This ensures that
53
- ``detect_project_name`` and source-root resolution use the *correct* root
54
- rather than the monorepo root, giving accurate import mappings.
52
+ Walks for marker files and returns their parent directories — one per
53
+ distinct Python sub-project. A marker at ``search_root`` does not hide
54
+ nested marker roots. This ensures that ``detect_project_name`` and
55
+ source-root resolution use the *correct* root rather than treating the
56
+ whole monorepo as one project.
55
57
 
56
58
  Falls back to ``[search_root]`` when no markers are found anywhere (the
57
59
  directory contains only bare .py scripts with no packaging metadata).
58
60
  """
59
- if _has_python_markers(search_root):
60
- return [search_root]
61
-
62
- roots: list[Path] = []
63
- for marker in PYTHON_MARKERS:
64
- for marker_file in sorted(search_root.rglob(marker)):
65
- rel_parts = marker_file.relative_to(search_root).parts
66
- if _EXCLUDED_DIRS & set(rel_parts):
67
- continue
68
- if marker == "pyproject.toml" and not (
69
- _pyproject_has_project_section(marker_file)
70
- ):
71
- continue
72
- candidate = marker_file.parent
73
- # Skip if already covered by a previously found (ancestor) root
74
- if any(
75
- candidate == r or candidate.is_relative_to(r)
76
- for r in roots
77
- ):
78
- continue
79
- roots.append(candidate)
80
-
81
- return sorted(roots) if roots else [search_root]
61
+ return collect_marker_roots(
62
+ search_root,
63
+ PYTHON_MARKERS,
64
+ excluded_dirs=_EXCLUDED_DIRS,
65
+ marker_filter=_is_valid_python_marker,
66
+ )
82
67
 
83
68
 
84
69
  def detect_project_name(project_root: Path) -> str:
@@ -121,14 +106,17 @@ def _has_python_markers(directory: Path) -> bool:
121
106
  path = directory / marker
122
107
  if not path.exists():
123
108
  continue
124
- if marker == "pyproject.toml":
125
- if _pyproject_has_project_section(path):
126
- return True
127
- else:
109
+ if _is_valid_python_marker(path):
128
110
  return True
129
111
  return False
130
112
 
131
113
 
114
+ def _is_valid_python_marker(path: Path) -> bool:
115
+ if path.name == "pyproject.toml":
116
+ return _pyproject_has_project_section(path)
117
+ return True
118
+
119
+
132
120
  def _pyproject_has_project_section(path: Path) -> bool:
133
121
  try:
134
122
  with path.open("rb") as f: