2121from  _pytask .console  import  create_summary_panel 
2222from  _pytask .console  import  get_file 
2323from  _pytask .exceptions  import  CollectionError 
24+ from  _pytask .exceptions  import  NodeNotCollectedError 
2425from  _pytask .mark_utils  import  get_all_marks 
2526from  _pytask .mark_utils  import  has_mark 
2627from  _pytask .node_protocols  import  PNode 
3738from  _pytask .path  import  shorten_path 
3839from  _pytask .reports  import  CollectionReport 
3940from  _pytask .shared  import  find_duplicates 
41+ from  _pytask .task_utils  import  COLLECTED_TASKS 
4042from  _pytask .task_utils  import  task  as  task_decorator 
4143from  _pytask .typing  import  is_task_function 
4244from  rich .text  import  Text 
@@ -61,6 +63,7 @@ def pytask_collect(session: Session) -> bool:
6163
6264    _collect_from_paths (session )
6365    _collect_from_tasks (session )
66+     _collect_not_collected_tasks (session )
6467
6568    session .tasks .extend (
6669        i .node 
@@ -108,6 +111,9 @@ def _collect_from_tasks(session: Session) -> None:
108111            path  =  get_file (raw_task )
109112            name  =  raw_task .pytask_meta .name 
110113
114+         if  has_mark (raw_task , "task" ):
115+             COLLECTED_TASKS [path ].remove (raw_task )
116+ 
111117        # When a task is not a callable, it can be anything or a PTask. Set arbitrary 
112118        # values and it will pass without errors and not collected. 
113119        else :
@@ -126,6 +132,45 @@ def _collect_from_tasks(session: Session) -> None:
126132            session .collection_reports .append (report )
127133
128134
135+ _FAILED_COLLECTING_TASK  =  """\  
136+  Failed to collect task '{name}'{path_desc}.
137+ 
138+ This can happen when the task function is defined in another module, imported to a \  
139+  task module and wrapped with the '@task' decorator.
140+ 
141+ To collect this task correctly, wrap the imported function in a lambda expression like 
142+ 
143+ task(...)(lambda **x: imported_function(**x)). 
144+ """ 
145+ 
146+ 
147+ def  _collect_not_collected_tasks (session : Session ) ->  None :
148+     """Collect tasks that are not collected yet and create failed reports.""" 
149+     for  path  in  list (COLLECTED_TASKS ):
150+         tasks  =  COLLECTED_TASKS .pop (path )
151+         for  task  in  tasks :
152+             name  =  task .pytask_meta .name   # type: ignore[attr-defined] 
153+             node : PTask 
154+             if  path :
155+                 node  =  Task (base_name = name , path = path , function = task )
156+                 path_desc  =  f" in '{ path }  '" 
157+             else :
158+                 node  =  TaskWithoutPath (name = name , function = task )
159+                 path_desc  =  "" 
160+             report  =  CollectionReport (
161+                 outcome = CollectionOutcome .FAIL ,
162+                 node = node ,
163+                 exc_info = (
164+                     NodeNotCollectedError ,
165+                     NodeNotCollectedError (
166+                         _FAILED_COLLECTING_TASK .format (name = name , path_desc = path_desc )
167+                     ),
168+                     None ,
169+                 ),
170+             )
171+             session .collection_reports .append (report )
172+ 
173+ 
129174@hookimpl  
130175def  pytask_ignore_collect (path : Path , config : dict [str , Any ]) ->  bool :
131176    """Ignore a path during the collection.""" 
0 commit comments