11import inspect
22import logging
3- import textwrap
43import typing as t
54import unittest
65import uuid
@@ -278,6 +277,8 @@ class PlanBuilt(BaseConsoleEvent):
278277]
279278
280279T = t .TypeVar ("T" )
280+ EventType = t .TypeVar ("EventType" , bound = BaseConsoleEvent )
281+
281282
282283def get_console_event_by_name (
283284 event_name : str ,
@@ -330,7 +331,9 @@ def __init_subclass__(cls):
330331 if camel_case_method_name in known_events :
331332 logger .debug (f"Creating { method_name } for { camel_case_method_name } " )
332333 signature = inspect .signature (getattr (Console , method_name ))
333- handler = cls .create_event_handler (method_name , camel_case_method_name , signature )
334+ event_cls = get_console_event_by_name (camel_case_method_name )
335+ assert event_cls is not None , f"Event { camel_case_method_name } not found"
336+ handler = cls .create_event_handler (method_name , event_cls , signature )
334337 setattr (cls , method_name , handler )
335338 else :
336339 logger .debug (f"Creating { method_name } for unknown event" )
@@ -339,70 +342,23 @@ def __init_subclass__(cls):
339342 setattr (cls , method_name , handler )
340343
341344 @classmethod
342- def create_event_handler (cls , method_name : str , event_name : str , signature : inspect .Signature ):
343- func_signature , call_params = cls .create_signatures_and_params (signature )
345+ def create_event_handler (cls , method_name : str , event_cls : type [BaseConsoleEvent ], signature : inspect .Signature ):
346+ """Create a GeneratedCallable for known events."""
347+ def handler (self , * args : t .Any , ** kwargs : t .Any ) -> None :
348+ callable_handler = GeneratedCallable (self , event_cls , signature , method_name )
349+ return callable_handler (self , * args , ** kwargs )
344350
345- event_handler_str = textwrap .dedent (f"""
346- def { method_name } ({ ", " .join (func_signature )} ):
347- self.publish_known_event('{ event_name } ', { ", " .join (call_params )} )
348- """ )
349- exec (event_handler_str )
350- return t .cast (t .Callable [[t .Any ], t .Any ], locals ()[method_name ])
351+ return handler
351352
352- @classmethod
353- def create_signatures_and_params (cls , signature : inspect .Signature ):
354- func_signature : list [str ] = []
355- call_params : list [str ] = []
356- for param_name , param in signature .parameters .items ():
357- if param_name == "self" :
358- func_signature .append ("self" )
359- continue
360-
361- # Handle *args - convert to unknown_args
362- if param .kind == inspect .Parameter .VAR_POSITIONAL :
363- param_type_name = param .annotation
364- if not isinstance (param_type_name , str ):
365- param_type_name = param_type_name .__name__
366- func_signature .append (f"*{ param_name } : '{ param_type_name } '" )
367- # Put *args into unknown_args instead of trying to pass as positional
368- call_params .append (f"_unknown_args_from_varargs=dict(enumerate({ param_name } ))" )
369- continue
370-
371- # Handle **kwargs
372- if param .kind == inspect .Parameter .VAR_KEYWORD :
373- param_type_name = param .annotation
374- if not isinstance (param_type_name , str ):
375- param_type_name = param_type_name .__name__
376- func_signature .append (f"**{ param_name } : '{ param_type_name } '" )
377- call_params .append (f"**{ param_name } " )
378- continue
379-
380- if param .default is inspect ._empty :
381- param_type_name = param .annotation
382- if not isinstance (param_type_name , str ):
383- param_type_name = param_type_name .__name__
384- func_signature .append (f"{ param_name } : '{ param_type_name } '" )
385- else :
386- default_value = param .default
387- param_type_name = param .annotation
388- if not isinstance (param_type_name , str ):
389- param_type_name = param_type_name .__name__
390- if isinstance (param .default , str ):
391- default_value = f"'{ param .default } '"
392- func_signature .append (f"{ param_name } : '{ param_type_name } ' = { default_value } " )
393- call_params .append (f"{ param_name } ={ param_name } " )
394- return (func_signature , call_params )
395353
396354 @classmethod
397355 def create_unknown_event_handler (cls , method_name : str , signature : inspect .Signature ):
398- func_signature , call_params = cls .create_signatures_and_params (signature )
356+ """Create an UnknownEventCallable for unknown events."""
357+ def handler (self , * args : t .Any , ** kwargs : t .Any ) -> None :
358+ callable_handler = UnknownEventCallable (self , method_name , signature )
359+ return callable_handler (self , * args , ** kwargs )
399360
400- event_handler_str = textwrap .dedent (f"""
401- def { method_name } ({ ", " .join (func_signature )} ):
402- self.publish_unknown_event('{ method_name } ', { ", " .join (call_params )} )
403- """ )
404- exec (event_handler_str )
405- return t .cast (t .Callable [[t .Any ], t .Any ], locals ()[method_name ])
361+ return handler
406362
407363 def __init__ (self , log_override : logging .Logger | None = None ) -> None :
408364 self ._handlers : dict [str , ConsoleEventHandler ] = {}
@@ -471,6 +427,92 @@ def capture_built_plan(self, plan: SQLMeshPlan) -> None:
471427 """Capture the built plan and publish a PlanBuilt event."""
472428 self .publish (PlanBuilt (plan = plan ))
473429
430+
431+ class GeneratedCallable (t .Generic [EventType ]):
432+ """A callable that dynamically handles console method invocations and converts them to events."""
433+
434+ def __init__ (
435+ self ,
436+ console : IntrospectingConsole ,
437+ event_cls : type [EventType ],
438+ original_signature : inspect .Signature ,
439+ method_name : str
440+ ):
441+ self .console = console
442+ self .event_cls = event_cls
443+ self .original_signature = original_signature
444+ self .method_name = method_name
445+
446+ def __call__ (self , * args : t .Any , ** kwargs : t .Any ) -> None :
447+ """Create an instance of the event class with the provided arguments."""
448+ # Bind arguments to the original signature
449+ try :
450+ bound = self .original_signature .bind (* args , ** kwargs )
451+ bound .apply_defaults ()
452+ except TypeError as e :
453+ # If binding fails, collect all args/kwargs as unknown
454+ self .console .logger .warning (f"Failed to bind arguments for { self .method_name } : { e } " )
455+ unknown_args = dict (enumerate (args [1 :])) # Skip 'self'
456+ unknown_args .update (kwargs )
457+ self ._create_and_publish_event ({}, unknown_args )
458+ return
459+
460+ # Process bound arguments
461+ bound_args = dict (bound .arguments )
462+ bound_args .pop ("self" , None ) # Remove self from arguments
463+
464+ self ._create_and_publish_event (bound_args , {})
465+
466+ def _create_and_publish_event (self , bound_args : dict [str , t .Any ], extra_unknown : dict [str , t .Any ]) -> None :
467+ """Create and publish the event with proper argument handling."""
468+ expected_fields = self .event_cls .__dataclass_fields__
469+ expected_kwargs : dict [str , t .Any ] = {}
470+ unknown_args : dict [str , t .Any ] = {}
471+
472+ # Add any extra unknown args first
473+ unknown_args .update (extra_unknown )
474+
475+ # Process bound arguments
476+ for key , value in bound_args .items ():
477+ if key in expected_fields :
478+ expected_kwargs [key ] = value
479+ else :
480+ unknown_args [key ] = value
481+
482+ # Create and publish the event
483+ event = self .event_cls (** expected_kwargs , unknown_args = unknown_args )
484+ self .console .publish (event )
485+
486+
487+ class UnknownEventCallable :
488+ """A callable for handling unknown console events."""
489+
490+ def __init__ (
491+ self ,
492+ console : "IntrospectingConsole" ,
493+ method_name : str ,
494+ original_signature : inspect .Signature
495+ ):
496+ self .console = console
497+ self .method_name = method_name
498+ self .original_signature = original_signature
499+
500+ def __call__ (self , * args : t .Any , ** kwargs : t .Any ) -> None :
501+ """Handle unknown event method calls."""
502+ # Bind arguments to the original signature
503+ try :
504+ bound = self .original_signature .bind (* args , ** kwargs )
505+ bound .apply_defaults ()
506+ bound_args = dict (bound .arguments )
507+ bound_args .pop ("self" , None ) # Remove self from arguments
508+ except TypeError :
509+ # If binding fails, collect all args/kwargs
510+ bound_args = dict (enumerate (args [1 :])) # Skip 'self'
511+ bound_args .update (kwargs )
512+
513+ self .console .publish_unknown_event (self .method_name , ** bound_args )
514+
515+
474516class EventConsole (IntrospectingConsole ):
475517 """
476518 A console implementation that manages and publishes events related to
0 commit comments