Call a function and return its result.
(self, func: Func[P, R], *args: P.args, **kwargs: P.kwargs)
| 430 | return result, fn |
| 431 | |
| 432 | async def call(self, func: Func[P, R], *args: P.args, **kwargs: P.kwargs) -> R: |
| 433 | """Call a function and return its result.""" |
| 434 | try: |
| 435 | # We could await based on the return value instead of checking function |
| 436 | # color but that would silently allow incorrect code which is |
| 437 | # especially bad if not intentional and we don't warn user about it. |
| 438 | result = func(*args, **kwargs) |
| 439 | if inspect.iscoroutinefunction(func): |
| 440 | result = await cast(typing.Awaitable[R], result) |
| 441 | except FunctionError: |
| 442 | # Escape hatch to fully control logging from user code. |
| 443 | raise |
| 444 | except dagger.QueryError as e: |
| 445 | tb = e.__traceback__ |
| 446 | # Exclude the line in "try" above |
| 447 | if tb: |
| 448 | tb = tb.tb_next |
| 449 | # Exclude the underlying TransportQueryError to reduce noise |
| 450 | e.__cause__ = None |
| 451 | logger.exception( |
| 452 | "API error while executing function", |
| 453 | exc_info=(type(e), e, tb), |
| 454 | ) |
| 455 | # Preserve API error so it's properly propagated. |
| 456 | raise e from None |
| 457 | except Exception as e: |
| 458 | # Escape hatch if too noisy. |
| 459 | if self.log_exceptions: |
| 460 | # Logging the exception will show the full stack trace on stderr. |
| 461 | logger.exception("Unhandled exception while executing function") |
| 462 | raise FunctionError(str(e)) from e |
| 463 | |
| 464 | return result |
| 465 | |
| 466 | async def structure(self, obj: Any, cl: type[T]) -> T: |
| 467 | """Convert a primitive value to the expected type.""" |
no test coverage detected