From 7f8b5f4029437d0feb2a54089e08e899fd1d27dc Mon Sep 17 00:00:00 2001 From: Jakub Cierocki Date: Mon, 15 Jul 2024 12:32:44 +0200 Subject: [PATCH] redesigned context-not-available error to follow the same principles as other IQL errors, inherting from IQLError, thus enabled its handling by self-reflection mechanism --- src/dbally/context/exceptions.py | 21 -------------- src/dbally/iql/_exceptions.py | 48 +++++++++++++++++++++++++++++--- src/dbally/iql/_processor.py | 12 +++----- 3 files changed, 48 insertions(+), 33 deletions(-) diff --git a/src/dbally/context/exceptions.py b/src/dbally/context/exceptions.py index 0efa1473..15c1d303 100644 --- a/src/dbally/context/exceptions.py +++ b/src/dbally/context/exceptions.py @@ -1,26 +1,5 @@ -from abc import ABC - - -class BaseContextException(Exception, ABC): - """ - A base (abstract) exception for all specification context-related exception. - """ - - class ContextNotAvailableError(Exception): """ An exception inheriting from BaseContextException pointining that no sufficient context information was provided by the user while calling view.ask(). """ - - -class ContextualisationNotAllowed(Exception): - """ - An exception inheriting from BaseContextException pointining that the filter method signature - does not allow to provide an additional context. - """ - - -# WORKAROUND - traditional inhertiance syntax is not working in context of abstract Exceptions -BaseContextException.register(ContextNotAvailableError) -BaseContextException.register(ContextualisationNotAllowed) diff --git a/src/dbally/iql/_exceptions.py b/src/dbally/iql/_exceptions.py index 7df08187..fdb5d349 100644 --- a/src/dbally/iql/_exceptions.py +++ b/src/dbally/iql/_exceptions.py @@ -1,13 +1,26 @@ import ast from typing import Optional, Union +from typing_extensions import TypeAlias from dbally.exceptions import DbAllyError +IQLNode: TypeAlias = Union[ast.stmt, ast.expr] + + class IQLError(DbAllyError): - """Base exception for all IQL parsing related exceptions.""" + """ + Base exception for all IQL parsing related exceptions. + + Attributes: + node: An IQL Node (AST Exprresion) during which processing an error was encountered. + source: Raw LLM response containing IQL filter calls. + """ + + node: IQLNode + source: str - def __init__(self, message: str, node: Union[ast.stmt, ast.expr], source: str) -> None: + def __init__(self, message: str, node: IQLNode, source: str) -> None: message = message + ": " + source[node.col_offset : node.end_col_offset] super().__init__(message) @@ -18,7 +31,7 @@ def __init__(self, message: str, node: Union[ast.stmt, ast.expr], source: str) - class IQLArgumentParsingError(IQLError): """Raised when an argument cannot be parsed into a valid IQL.""" - def __init__(self, node: Union[ast.stmt, ast.expr], source: str) -> None: + def __init__(self, node: IQLNode, source: str) -> None: message = "Not a valid IQL argument" super().__init__(message, node, source) @@ -26,7 +39,7 @@ def __init__(self, node: Union[ast.stmt, ast.expr], source: str) -> None: class IQLUnsupportedSyntaxError(IQLError): """Raised when trying to parse an unsupported syntax.""" - def __init__(self, node: Union[ast.stmt, ast.expr], source: str, context: Optional[str] = None) -> None: + def __init__(self, node: IQLNode, source: str, context: Optional[str] = None) -> None: node_name = node.__class__.__name__ message = f"{node_name} syntax is not supported in IQL" @@ -47,3 +60,30 @@ def __init__(self, node: ast.Name, source: str) -> None: class IQLArgumentValidationError(IQLError): """Raised when argument is not valid for a given method.""" + + +class IQLContextNotAllowedError(IQLError): + """ + Raised when a context call/keyword has been passed as an argument to the filter + which does not support contextualization for this specific parameter. + """ + + def __init__( + self, + node: IQLNode, + source: str, + arg_name: Optional[str] = None + ) -> None: + + if arg_name is None: + message = ( + "The LLM detected that the context is required to execute the query" + "while the filter signature does not allow it at all." + ) + else: + message = ( + "The LLM detected that the context is required to execute the query" + f"while the filter signature does allow it for `{arg_name}` argument." + ) + + super().__init__(message, node, source) diff --git a/src/dbally/iql/_processor.py b/src/dbally/iql/_processor.py index 7393aa87..dd7b640e 100644 --- a/src/dbally/iql/_processor.py +++ b/src/dbally/iql/_processor.py @@ -12,6 +12,7 @@ IQLError, IQLFunctionNotExists, IQLUnsupportedSyntaxError, + IQLContextNotAllowedError ) from dbally.iql._type_validators import validate_arg_type from dbally.views.exposed_functions import ExposedFunction, MethodParamWithTyping @@ -134,6 +135,7 @@ def _parse_arg( arg_spec: Optional[MethodParamWithTyping] = None, parent_func_def: Optional[ExposedFunction] = None, ) -> Any: + if isinstance(arg, ast.List): return [self._parse_arg(x) for x in arg.elts] @@ -143,16 +145,10 @@ def _parse_arg( raise IQLArgumentParsingError(arg, self.source) if parent_func_def.context_class is None: - raise ContextualisationNotAllowed( - "The LLM detected that the context is required +\ - to execute the query while the filter signature does not allow it at all." - ) + raise IQLContextNotAllowedError(arg, self.source) if not _does_arg_allow_context(arg_spec): - raise ContextualisationNotAllowed( - f"The LLM detected that the context is required +\ - to execute the query while the filter signature does allow it for `{arg_spec.name}` argument." - ) + raise IQLContextNotAllowedError(arg, self.source, arg_name=arg_spec.name) return parent_func_def.context_class.select_context(self.contexts)