Skip to content

Commit f39970d

Browse files
committed
gh-158986: Fix profiling.sampling module type for nested site-packages
extract_module_name() checked the stdlib directory first, so files in a site-packages directory inside it were reported as stdlib modules. Use the most specific matching directory instead.
1 parent 9d22a53 commit f39970d

3 files changed

Lines changed: 34 additions & 8 deletions

File tree

‎Lib/profiling/sampling/module_utils.py‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,17 @@ def extract_module_name(filename, path_info):
6565
except (ValueError, OSError):
6666
return (str(filename), 'other')
6767

68-
# Check if it's in stdlib
69-
if path_info['stdlib'] and file_path.is_relative_to(path_info['stdlib']):
70-
return (_path_to_module(file_path.relative_to(path_info['stdlib'])), 'stdlib')
71-
72-
# Check site-packages
73-
for site_pkg in path_info['site_packages']:
74-
if file_path.is_relative_to(site_pkg):
75-
return (_path_to_module(file_path.relative_to(site_pkg)), 'site-packages')
68+
# Check stdlib and site-packages, preferring the most specific directory
69+
# because site-packages is usually located inside the stdlib directory
70+
bases = [(path_info['stdlib'], 'stdlib')]
71+
bases.extend((site_pkg, 'site-packages')
72+
for site_pkg in path_info['site_packages'])
73+
matches = [(base, module_type) for base, module_type in bases
74+
if base and file_path.is_relative_to(base)]
75+
if matches:
76+
# max() returns the first of equal candidates, so stdlib wins ties
77+
base, module_type = max(matches, key=lambda m: len(m[0].parts))
78+
return (_path_to_module(file_path.relative_to(base)), module_type)
7679

7780
# Check other sys.path entries (project files)
7881
if not str(file_path).startswith(('<', '[')): # Skip special files

‎Lib/test/test_profiling/test_heatmap.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,25 @@ def test_extract_module_name_with_stdlib_file(self):
8686
self.assertEqual(module_type, 'stdlib')
8787
self.assertIn('os', module_name)
8888

89+
def test_extract_module_name_with_site_packages_inside_stdlib(self):
90+
"""Test extract_module_name when site-packages is inside stdlib."""
91+
stdlib = Path('/usr/lib/python3.15')
92+
site_packages = stdlib / 'site-packages'
93+
# On Windows, site.getsitepackages() also includes sys.prefix
94+
path_info = {
95+
'stdlib': stdlib,
96+
'site_packages': [Path('/usr'), site_packages],
97+
'sys_path': [],
98+
}
99+
100+
site_packages_file = str(site_packages / 'requests' / 'api.py')
101+
self.assertEqual(extract_module_name(site_packages_file, path_info),
102+
('requests.api', 'site-packages'))
103+
104+
stdlib_file = str(stdlib / 'json' / 'decoder.py')
105+
self.assertEqual(extract_module_name(stdlib_file, path_info),
106+
('json.decoder', 'stdlib'))
107+
89108
def test_extract_module_name_with_project_file(self):
90109
"""Test extract_module_name with a project file."""
91110
path_info = get_python_path_info()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix :mod:`profiling.sampling` reporting modules in a ``site-packages``
2+
directory located inside the standard library directory as standard library
3+
modules. The heatmap now lists them under "Site Packages" and flamegraph
4+
labels no longer start with ``site-packages.``.

0 commit comments

Comments
 (0)