From 689dfe8673ea79dca7156b0dd2495d615dca1349 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Thu, 30 Nov 2023 08:36:19 -0800 Subject: [PATCH] map funcs --- .../script/functions/map_functions.py | 10 +++- src/ytdl_sub/script/types/function.py | 3 +- src/ytdl_sub/script/utils/exceptions.py | 42 +++++++++------- .../script/functions/test_map_functions.py | 48 +++++++++++++++++++ 4 files changed, 84 insertions(+), 19 deletions(-) create mode 100644 tests/unit/script/functions/test_map_functions.py diff --git a/src/ytdl_sub/script/functions/map_functions.py b/src/ytdl_sub/script/functions/map_functions.py index 0559336d..bd85a55d 100644 --- a/src/ytdl_sub/script/functions/map_functions.py +++ b/src/ytdl_sub/script/functions/map_functions.py @@ -3,6 +3,7 @@ from typing import Optional from ytdl_sub.script.types.map import Map from ytdl_sub.script.types.resolvable import AnyArgument from ytdl_sub.script.types.resolvable import Hashable +from ytdl_sub.script.utils.exceptions import KeyDoesNotExistRuntimeException class MapFunctions: @@ -12,6 +13,11 @@ class MapFunctions: Return ``key``'s value within the Map. If ``key`` does not exist, and ``default`` is provided, it will return ``default``. Otherwise, will error. """ - if default is not None: - return mapping.value.get(key, default=default) + if key not in mapping.value: + if default is not None: + return default + + raise KeyDoesNotExistRuntimeException( + f"Tried to call %map_get with key {key.value}, but it does not exist" + ) return mapping.value[key] diff --git a/src/ytdl_sub/script/types/function.py b/src/ytdl_sub/script/types/function.py index 34fd8a47..8940dab0 100644 --- a/src/ytdl_sub/script/types/function.py +++ b/src/ytdl_sub/script/types/function.py @@ -26,6 +26,7 @@ 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 UNREACHABLE from ytdl_sub.script.utils.exceptions import FunctionRuntimeException +from ytdl_sub.script.utils.exceptions import RuntimeException from ytdl_sub.script.utils.exceptions import UserThrownRuntimeError from ytdl_sub.script.utils.type_checking import FunctionSpec from ytdl_sub.script.utils.type_checking import is_union @@ -189,7 +190,7 @@ class BuiltInFunction(Function, BuiltInFunctionType): try: return self.callable(*resolved_arguments) - except UserThrownRuntimeError: + except (UserThrownRuntimeError, RuntimeException): raise except Exception as exc: raise FunctionRuntimeException( diff --git a/src/ytdl_sub/script/utils/exceptions.py b/src/ytdl_sub/script/utils/exceptions.py index a4cd169f..b6845971 100644 --- a/src/ytdl_sub/script/utils/exceptions.py +++ b/src/ytdl_sub/script/utils/exceptions.py @@ -2,15 +2,14 @@ from abc import ABC from ytdl_sub.utils.exceptions import ValidationException +################################################################################################### +# USER EXCEPTIONS + class UserException(ValidationException, ABC): """It's the user's fault!""" -class RuntimeException(ValueError, ABC): - """Exception thrown at runtime during resolution""" - - class InvalidSyntaxException(UserException): """Syntax is incorrect""" @@ -47,18 +46,6 @@ class CycleDetected(UserException): """A cycle exists within a user's script""" -class FunctionRuntimeException(RuntimeException): - """Exception thrown when a ytdl-sub function has an error occur at runtime""" - - -class KeyNotHashableRuntimeException(RuntimeException): - """Map tried to use a non-hashable key at runtime""" - - -class FunctionDoesNotExistRuntimeException(RuntimeException): - """Tried to get a function that does not exist""" - - class UserThrownRuntimeError(ValidationException): """An error explicitly thrown by the user via a function""" @@ -72,3 +59,26 @@ UNREACHABLE = _UnreachableSyntaxException( "Please upload your config/subscription file(s) to and make a GitHub issue at " "https://github.com/jmbannon/ytdl-sub/issues" ) + +################################################################################################### +# RUNTIME EXCEPTIONS + + +class RuntimeException(ValueError, ABC): + """Exception thrown at runtime during resolution""" + + +class FunctionRuntimeException(RuntimeException): + """Exception thrown when a ytdl-sub function has an error occur at runtime""" + + +class KeyNotHashableRuntimeException(RuntimeException): + """Map tried to use a non-hashable key at runtime""" + + +class FunctionDoesNotExistRuntimeException(RuntimeException): + """Tried to get a function that does not exist""" + + +class KeyDoesNotExistRuntimeException(RuntimeException): + """Tried to access a key on a map that does not exist, with no default""" diff --git a/tests/unit/script/functions/test_map_functions.py b/tests/unit/script/functions/test_map_functions.py new file mode 100644 index 00000000..a908e776 --- /dev/null +++ b/tests/unit/script/functions/test_map_functions.py @@ -0,0 +1,48 @@ +import re + +import pytest + +from ytdl_sub.script.script import Script +from ytdl_sub.script.utils.exceptions import KeyDoesNotExistRuntimeException + + +class TestMapFunctions: + def test_map_get(self): + output = ( + Script( + { + "input_map": "{{'key': 'value'}}", + "output": "{%map_get(input_map, 'key')}", + } + ) + .resolve(update=True) + .get("output") + .native + ) + assert output == "value" + + def test_map_get_optional(self): + output = ( + Script( + { + "input_map": "{{'key': 'value'}}", + "output": "{%map_get(input_map, 'dne', 'optional_value')}", + } + ) + .resolve(update=True) + .get("output") + .native + ) + assert output == "optional_value" + + def test_map_get_errors_missing_key(self): + with pytest.raises( + KeyDoesNotExistRuntimeException, + match=re.escape("Tried to call %map_get with key dne, but it does not exist"), + ): + Script( + { + "input_map": "{{'key': 'value'}}", + "output": "{%map_get(input_map, 'dne')}", + } + ).resolve()