Skip to content

Commit 5883c3b

Browse files
richbanclaude
andcommitted
fix: refactor exec() approach with proper callable classes
Replace dynamic exec() generation with proper callable class pattern for better maintainability, type safety, and debugging capability. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 07ef9d3 commit 5883c3b

File tree

1 file changed

+102
-60
lines changed

1 file changed

+102
-60
lines changed

dagster_sqlmesh/console.py

Lines changed: 102 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import inspect
22
import logging
3-
import textwrap
43
import typing as t
54
import unittest
65
import uuid
@@ -278,6 +277,8 @@ class PlanBuilt(BaseConsoleEvent):
278277
]
279278

280279
T = t.TypeVar("T")
280+
EventType = t.TypeVar("EventType", bound=BaseConsoleEvent)
281+
281282

282283
def 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+
474516
class EventConsole(IntrospectingConsole):
475517
"""
476518
A console implementation that manages and publishes events related to

0 commit comments

Comments
 (0)