@@ -68,24 +68,9 @@ def clear_import_graph_caches() -> None:
6868 "lib" : [], # No Python lib (Rust implementation)
6969 "hard_deps" : ["_pylong.py" ],
7070 },
71- # Pure Python implementations
72- "abc" : {
73- "hard_deps" : ["_py_abc.py" ],
74- },
75- "codecs" : {
76- "hard_deps" : ["_pycodecs.py" ],
77- },
78- "datetime" : {
79- "hard_deps" : ["_pydatetime.py" ],
80- },
81- "decimal" : {
82- "hard_deps" : ["_pydecimal.py" ],
83- },
84- "io" : {
85- "hard_deps" : ["_pyio.py" ],
86- },
87- "warnings" : {
88- "hard_deps" : ["_py_warnings.py" ],
71+ # Non-pattern hard_deps (can't be auto-detected)
72+ "ast" : {
73+ "hard_deps" : ["_ast_unparse.py" ],
8974 },
9075 # Data directories
9176 "pydoc" : {
@@ -102,29 +87,52 @@ def clear_import_graph_caches() -> None:
10287}
10388
10489
105- def resolve_hard_dep_parent (name : str ) -> str | None :
90+ def resolve_hard_dep_parent (name : str , cpython_prefix : str = "cpython" ) -> str | None :
10691 """Resolve a hard_dep name to its parent module.
10792
108- If 'name' is listed as a hard_dep of another module, return that module's name.
109- E.g., 'pydoc_data' -> 'pydoc', '_pydatetime' -> 'datetime'
93+ Only returns a parent if the file is actually tracked:
94+ - Explicitly listed in DEPENDENCIES as a hard_dep
95+ - Or auto-detected _py{module}.py pattern where the parent module exists
11096
11197 Args:
11298 name: Module or file name (with or without .py extension)
99+ cpython_prefix: CPython directory prefix
113100
114101 Returns:
115- Parent module name if found, None otherwise
102+ Parent module name if found and tracked , None otherwise
116103 """
117104 # Normalize: remove .py extension if present
118105 if name .endswith (".py" ):
119106 name = name [:- 3 ]
120107
108+ # Check DEPENDENCIES table first (explicit hard_deps)
121109 for module_name , dep_info in DEPENDENCIES .items ():
122110 hard_deps = dep_info .get ("hard_deps" , [])
123111 for dep in hard_deps :
124112 # Normalize dep: remove .py extension
125113 dep_normalized = dep [:- 3 ] if dep .endswith (".py" ) else dep
126114 if dep_normalized == name :
127115 return module_name
116+
117+ # Auto-detect _py{module} or _py_{module} patterns
118+ # Only if the parent module actually exists
119+ if name .startswith ("_py" ):
120+ if name .startswith ("_py_" ):
121+ # _py_abc -> abc
122+ parent = name [4 :]
123+ else :
124+ # _pydatetime -> datetime
125+ parent = name [3 :]
126+
127+ # Verify the parent module exists
128+ lib_dir = pathlib .Path (cpython_prefix ) / "Lib"
129+ parent_file = lib_dir / f"{ parent } .py"
130+ parent_dir = lib_dir / parent
131+ if parent_file .exists () or (
132+ parent_dir .exists () and (parent_dir / "__init__.py" ).exists ()
133+ ):
134+ return parent
135+
128136 return None
129137
130138
@@ -234,10 +242,16 @@ def get_lib_paths(
234242 # Default: try file first, then directory
235243 paths = [resolve_module_path (name , cpython_prefix , prefer = "file" )]
236244
237- # Add hard_deps
245+ # Add hard_deps from DEPENDENCIES
238246 for dep in dep_info .get ("hard_deps" , []):
239247 paths .append (construct_lib_path (cpython_prefix , dep ))
240248
249+ # Auto-detect _py{module}.py or _py_{module}.py patterns
250+ for pattern in [f"_py{ name } .py" , f"_py_{ name } .py" ]:
251+ auto_path = construct_lib_path (cpython_prefix , pattern )
252+ if auto_path .exists () and auto_path not in paths :
253+ paths .append (auto_path )
254+
241255 return tuple (paths )
242256
243257
0 commit comments