From 31b6d00af9058f6f6602f0506ff444770724bc29 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Sat, 18 Nov 2023 22:07:26 -0800 Subject: [PATCH] error functions --- src/ytdl_sub/script/functions/__init__.py | 6 ++++-- ..._functions.py => conditional_functions.py} | 2 +- .../script/functions/error_functions.py | 16 ++++++++++++++++ src/ytdl_sub/script/types/function.py | 11 ++++++++++- src/ytdl_sub/script/utils/exceptions.py | 12 ++++++++++-- tests/unit/script/types/test_function.py | 19 +++++++++++++++++++ 6 files changed, 60 insertions(+), 6 deletions(-) rename src/ytdl_sub/script/functions/{special_functions.py => conditional_functions.py} (94%) create mode 100644 src/ytdl_sub/script/functions/error_functions.py diff --git a/src/ytdl_sub/script/functions/__init__.py b/src/ytdl_sub/script/functions/__init__.py index 49c09a5b..f1404036 100644 --- a/src/ytdl_sub/script/functions/__init__.py +++ b/src/ytdl_sub/script/functions/__init__.py @@ -1,17 +1,19 @@ from ytdl_sub.script.functions.array_functions import ArrayFunctions from ytdl_sub.script.functions.boolean_functions import BooleanFunctions +from ytdl_sub.script.functions.conditional_functions import ConditionalFunctions +from ytdl_sub.script.functions.error_functions import ErrorFunctions from ytdl_sub.script.functions.map_functions import MapFunctions from ytdl_sub.script.functions.numeric_functions import NumericFunctions -from ytdl_sub.script.functions.special_functions import SpecialFunctions from ytdl_sub.script.functions.string_functions import StringFunctions class Functions( StringFunctions, NumericFunctions, - SpecialFunctions, + ConditionalFunctions, ArrayFunctions, MapFunctions, BooleanFunctions, + ErrorFunctions, ): pass diff --git a/src/ytdl_sub/script/functions/special_functions.py b/src/ytdl_sub/script/functions/conditional_functions.py similarity index 94% rename from src/ytdl_sub/script/functions/special_functions.py rename to src/ytdl_sub/script/functions/conditional_functions.py index 76e47839..e6383e9f 100644 --- a/src/ytdl_sub/script/functions/special_functions.py +++ b/src/ytdl_sub/script/functions/conditional_functions.py @@ -5,7 +5,7 @@ from ytdl_sub.script.types.resolvable import AnyTypeReturnableB from ytdl_sub.script.types.resolvable import Boolean -class SpecialFunctions: +class ConditionalFunctions: @staticmethod def if_( condition: Boolean, true: AnyTypeReturnableA, false: AnyTypeReturnableB diff --git a/src/ytdl_sub/script/functions/error_functions.py b/src/ytdl_sub/script/functions/error_functions.py new file mode 100644 index 00000000..a76dffe9 --- /dev/null +++ b/src/ytdl_sub/script/functions/error_functions.py @@ -0,0 +1,16 @@ +from ytdl_sub.script.types.resolvable import AnyType +from ytdl_sub.script.types.resolvable import Boolean +from ytdl_sub.script.types.resolvable import String +from ytdl_sub.script.utils.exceptions import UserThrownRuntimeError + + +class ErrorFunctions: + @staticmethod + def throw(error_message: String) -> AnyType: + raise UserThrownRuntimeError(error_message) + + @staticmethod + def assert_(condition: Boolean, assert_message: String) -> Boolean: + if not condition.value: + raise UserThrownRuntimeError(assert_message) + return condition diff --git a/src/ytdl_sub/script/types/function.py b/src/ytdl_sub/script/types/function.py index 3e01bcee..904c2c02 100644 --- a/src/ytdl_sub/script/types/function.py +++ b/src/ytdl_sub/script/types/function.py @@ -23,6 +23,8 @@ from ytdl_sub.script.types.variable import FunctionArgument from ytdl_sub.script.types.variable import Variable from ytdl_sub.script.types.variable_dependency import VariableDependency from ytdl_sub.script.utils.exception_formatters import FunctionArgumentsExceptionFormatter +from ytdl_sub.script.utils.exceptions import FunctionRuntimeException +from ytdl_sub.script.utils.exceptions import UserThrownRuntimeError from ytdl_sub.script.utils.type_checking import FunctionInputSpec from ytdl_sub.script.utils.type_checking import is_union from ytdl_sub.utils.exceptions import StringFormattingException @@ -167,4 +169,11 @@ class BuiltInFunction(Function, TypeHintedFunctionType): for arg in self.args ] - return self.callable(*resolved_args) + try: + return self.callable(*resolved_args) + except UserThrownRuntimeError: + raise + except Exception as exc: + raise FunctionRuntimeException( + f"Runtime error occurred when executing the function %{self.name}: {str(exc)}" + ) from exc diff --git a/src/ytdl_sub/script/utils/exceptions.py b/src/ytdl_sub/script/utils/exceptions.py index f62b5382..fe7f0f59 100644 --- a/src/ytdl_sub/script/utils/exceptions.py +++ b/src/ytdl_sub/script/utils/exceptions.py @@ -15,11 +15,19 @@ class IncompatibleFunctionArguments(UserException): """Function has invalid arguments""" -class UnreachableSyntaxException(InvalidSyntaxException): +class FunctionRuntimeException(ValueError): + """Exception thrown when a ytdl-sub function has an error occur at runtime""" + + +class UserThrownRuntimeError(ValidationException): + """An error explicitly thrown by the user via a function""" + + +class _UnreachableSyntaxException(InvalidSyntaxException): """For use in places where code _should_ never reach, but might from bugs""" -UNREACHABLE = UnreachableSyntaxException( +UNREACHABLE = _UnreachableSyntaxException( "If you see this error, you have discovered a bug in the script parser!\n" "Please upload your config/subscription file(s) to and make a GitHub issue at " "https://github.com/jmbannon/ytdl-sub/issues" diff --git a/tests/unit/script/types/test_function.py b/tests/unit/script/types/test_function.py index e8cd5ed1..f9e491c5 100644 --- a/tests/unit/script/types/test_function.py +++ b/tests/unit/script/types/test_function.py @@ -5,7 +5,9 @@ import pytest from ytdl_sub.script.script import Script from ytdl_sub.script.types.resolvable import Boolean from ytdl_sub.script.types.resolvable import String +from ytdl_sub.script.utils.exceptions import FunctionRuntimeException from ytdl_sub.script.utils.exceptions import IncompatibleFunctionArguments +from ytdl_sub.script.utils.exceptions import UserThrownRuntimeError def _incompatible_arguments_match(expected: str, recieved: str) -> str: @@ -78,3 +80,20 @@ class TestFunction: match=_incompatible_arguments_match(expected="Array, Integer", recieved="Map, Integer"), ): Script({"func": function_str}).resolve() + + def test_runtime_error(self): + with pytest.raises( + FunctionRuntimeException, + match=re.escape( + "Runtime error occurred when executing the function %div: division by zero" + ), + ): + Script({"divide_by_zero": "{%div(8820, 0)}"}).resolve() + + def test_user_throw(self): + with pytest.raises(UserThrownRuntimeError, match=re.escape("test this error message")): + Script({"throw_error": "{%throw('test this error message')}"}).resolve() + + def test_user_assert(self): + with pytest.raises(UserThrownRuntimeError, match=re.escape("test this error message")): + Script({"throw_error": "{%assert(False, 'test this error message')}"}).resolve()