2626from create_dependency_mapping import find_priority_list
2727from libcst import ClassDef , CSTVisitor
2828from libcst import matchers as m
29+ from libcst .helpers import get_absolute_module_from_package_for_import
2930from libcst .metadata import MetadataWrapper , ScopeProvider
3031from modular_integrations import (
3132 EXCLUDED_EXTERNAL_FILES ,
@@ -1221,7 +1222,30 @@ def find_file_type(class_name: str, model_name: str) -> str:
12211222)
12221223
12231224# These specific modeling imports should not be visited as other modeling files
1224- IMPORTS_TO_SKIP_IN_MODULAR = ("auto.modeling_auto" ,)
1225+ MODEL_NAMES_TO_SKIP_IN_MODULAR = {"auto" }
1226+
1227+
1228+ # Stands for the model of the file containing the import, e.g. the current model directory name
1229+ # in `from .configuration_llama import LlamaConfig`
1230+ CURRENT_MODEL = "__current_model__"
1231+
1232+
1233+ def get_imported_model_name (node : cst .ImportFrom ) -> str | None :
1234+ """Return the model directory `node` imports from, e.g. `llama` for `from ..llama.modeling_llama import X`,
1235+ `CURRENT_MODEL` for `from .configuration_llama import LlamaConfig`, or None if it does not import from a model."""
1236+ absolute_name = get_absolute_module_from_package_for_import (f"transformers.models.{ CURRENT_MODEL } " , node )
1237+ match = re .search (r"(?:^|\.)transformers\.models\.(\w+)" , absolute_name or "" )
1238+ return match .group (1 ) if match is not None else None
1239+
1240+
1241+ def is_import_from_other_model (node : cst .CSTNode ) -> bool :
1242+ """Whether `node` is an import statement from another model directory than the current model or MODEL_NAMES_TO_SKIP_IN_MODULAR."""
1243+ is_import_from = m .matches (node , m .SimpleStatementLine (body = [m .ImportFrom ()]))
1244+ return is_import_from and get_imported_model_name (node .body [0 ]) not in {
1245+ None ,
1246+ CURRENT_MODEL ,
1247+ * MODEL_NAMES_TO_SKIP_IN_MODULAR ,
1248+ }
12251249
12261250
12271251def append_new_import_node (
@@ -1480,9 +1504,16 @@ def visit_ImportFrom(self, node: cst.ImportFrom) -> None:
14801504 # `node.module` is None for fully relative imports, e.g. `from ... import initialization as init`
14811505 import_module = self .python_module .code_for_node (node .module ) if node .module is not None else ""
14821506 import_statement = "." * len (node .relative ) + import_module
1483- if any (import_to_skip in import_statement for import_to_skip in IMPORTS_TO_SKIP_IN_MODULAR ):
1507+ imported_model_name = get_imported_model_name (node )
1508+ if imported_model_name in MODEL_NAMES_TO_SKIP_IN_MODULAR :
14841509 return
1485- if m .matches (node .module , m .Attribute ()):
1510+ elif get_imported_model_name (node ) == self .model_name :
1511+ # do not allow imports like `from transformers.models.current_model import ...` or `from ...models.current_model import ...`
1512+ raise ValueError (
1513+ f"`{ self .python_module .code_for_node (node )} ` imports from `{ self .model_name } ` itself. Import files from "
1514+ f"`{ self .model_name } ` with a single-dot relative import instead, e.g. `from .configuration_{ self .model_name } import ...`."
1515+ )
1516+ elif m .matches (node .module , m .Attribute ()):
14861517 for imported_ in node .names :
14871518 # If we match here, it's an import from 3rd party lib that we need to skip
14881519 if any (external_file ["name" ] in import_statement for external_file in self .excluded_external_files ):
@@ -1517,6 +1548,11 @@ def visit_ImportFrom(self, node: cst.ImportFrom) -> None:
15171548 f"You are importing from { import_module } directly using global imports. Import from the correct local path"
15181549 )
15191550
1551+ def visit_If (self , node ):
1552+ # do not add imports from other models to the list of imports
1553+ statements = [stmt for stmt in node .body .body if not is_import_from_other_model (stmt )]
1554+ super ().visit_If (node .with_changes (body = node .body .with_changes (body = statements )))
1555+
15201556 def visit_SimpleStatementLine (self , node ):
15211557 """If we visit an import statement not previously visited, record it. If we visit a module-scope assignment,
15221558 simply record it or, if it is `__all__`, split it between files where we should dispatch it.
@@ -1538,11 +1574,8 @@ def visit_SimpleStatementLine(self, node):
15381574 self .python_module .code_for_node (node .body [0 ].module ) if node .body [0 ].module is not None else ""
15391575 )
15401576 import_statement = "." * len (node .body [0 ].relative ) + import_module
1541- if any (
1577+ if not is_import_from_other_model ( node ) or any (
15421578 external_file ["name" ] in import_statement for external_file in self .excluded_external_files
1543- ) or not (
1544- re .search (rf"(?:transformers\.models\.)|(?:\.\.)\w+\.({ self .match_patterns } ).*" , import_statement )
1545- and not any (import_to_skip in import_statement for import_to_skip in IMPORTS_TO_SKIP_IN_MODULAR )
15461579 ):
15471580 self .imports .append (node )
15481581 elif m .matches (node , simple_top_level_assign_structure ):
@@ -1617,6 +1650,11 @@ def leave_Module(self, node):
16171650
16181651 self .imported_objects_per_file [file_type ].update (mapper .objects_imported_from_modeling )
16191652
1653+ # Functions explicitly imported in the modular are inlined, even if the file of an inherited class imports them
1654+ # from a sibling file
1655+ for imported_objects in self .imported_objects_per_file .values ():
1656+ imported_objects .difference_update (self .added_objects_file_mapping )
1657+
16201658 def merge_model_specific_imports (self , visited_modules ):
16211659 """Merge the functions and assignments imported from the modeling files to the modular nodes and dependency graph,
16221660 based on the visited files."""
0 commit comments