From acceb50377d76a704b996fc4736741e6d8113d74 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Wed, 15 Nov 2023 23:16:15 -0800 Subject: [PATCH] linting --- src/ytdl_sub/script/parser.py | 14 ++++++------ src/ytdl_sub/script/types/array.py | 2 +- src/ytdl_sub/script/types/function.py | 28 +++++++++++++++++++----- src/ytdl_sub/script/types/map.py | 2 +- src/ytdl_sub/script/types/resolvable.py | 18 ++++++++------- tests/unit/script/types/test_function.py | 9 ++++++-- 6 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/ytdl_sub/script/parser.py b/src/ytdl_sub/script/parser.py index 2b9f6557..6ed6a1df 100644 --- a/src/ytdl_sub/script/parser.py +++ b/src/ytdl_sub/script/parser.py @@ -222,13 +222,13 @@ class _Parser: self._set_highlight_position() raise NUMERICS_INVALID_CHAR - if numeric_string == "." or numeric_string == "-": + if numeric_string in (".", "-"): raise NUMERICS_INVALID_CHAR try: numeric_float = float(numeric_string) - except ValueError: - raise UNREACHABLE + except ValueError as exc: + raise UNREACHABLE from exc if (numeric_int := int(numeric_float)) == numeric_float: return Integer(value=numeric_int) @@ -351,10 +351,10 @@ class _Parser: if ch == "]": self._pos += 1 return UnresolvedArray(value=function_args) - else: - function_args = self._parse_args( - argument_parser=ArgumentParser.ARRAY, breaking_chars="]" - ) + + function_args = self._parse_args( + argument_parser=ArgumentParser.ARRAY, breaking_chars="]" + ) raise UNREACHABLE diff --git a/src/ytdl_sub/script/types/array.py b/src/ytdl_sub/script/types/array.py index 182f575f..43a15955 100644 --- a/src/ytdl_sub/script/types/array.py +++ b/src/ytdl_sub/script/types/array.py @@ -19,7 +19,7 @@ class Array(NonHashable): value: List[Resolvable] @classmethod - def human_readable_name(cls) -> str: + def type_name(cls) -> str: return "Array" diff --git a/src/ytdl_sub/script/types/function.py b/src/ytdl_sub/script/types/function.py index 4d346785..b6ca5632 100644 --- a/src/ytdl_sub/script/types/function.py +++ b/src/ytdl_sub/script/types/function.py @@ -18,6 +18,7 @@ from ytdl_sub.script.types.resolvable import AnyType_0 from ytdl_sub.script.types.resolvable import AnyType_1 from ytdl_sub.script.types.resolvable import AnyType_2 from ytdl_sub.script.types.resolvable import ArgumentType +from ytdl_sub.script.types.resolvable import NamedType from ytdl_sub.script.types.resolvable import Resolvable from ytdl_sub.script.types.variable import FunctionArgument from ytdl_sub.script.types.variable import Variable @@ -30,6 +31,14 @@ def is_union(arg_type: Type) -> bool: return get_origin(arg_type) is Union +def is_optional(arg_type: Type) -> bool: + return is_union(arg_type) and type(None) in arg_type.__args__ + + +def get_optional_type(optional_type: Type) -> Type[NamedType]: + return [arg for arg in optional_type.__args__ if arg != type(None)][0] + + @dataclass(frozen=True) class FunctionInputSpec: args: Optional[List[Type[Resolvable | Optional[Resolvable]]]] = None @@ -110,10 +119,17 @@ class FunctionInputSpec: assert False, "should never reach here" def expected_args_str(self) -> str: + def to_human_readable_name(python_type: Type[NamedType] | Type[Union[NamedType]]) -> str: + if is_optional(python_type): + return f"Optional[{to_human_readable_name(get_optional_type(python_type))}]" + if is_union(python_type): + return ", ".join(to_human_readable_name(arg) for arg in python_type.__args__) + return python_type.type_name() + if self.args is not None: - return f"({', '.join([type_.__name__ for type_ in self.args])})" + return f"({', '.join([to_human_readable_name(type_) for type_ in self.args])})" elif self.varargs is not None: - return f"({self.varargs.__name__}, ...)" + return f"({to_human_readable_name(self.varargs.__name__)}, ...)" @classmethod def from_function(cls, func: "BuiltInFunction") -> "FunctionInputSpec": @@ -210,12 +226,12 @@ class BuiltInFunction(Function): if is_union(arg.output_type): # TODO: Move naming to separate function, deal with Union input naming received_type_names.append( - f"%{arg.name}(...)->Union[{', '.join(arg_type.human_readable_name() for arg_type in arg.output_type.__args__)}]" + f"%{arg.name}(...)->Union[{', '.join(arg_type.type_name() for arg_type in arg.output_type.__args__)}]" ) else: - received_type_names.append(f"%{arg.name}(...)->{arg.output_type.__name__}") + received_type_names.append(f"%{arg.name}(...)->{arg.output_type.type_name()}") else: - received_type_names.append(arg.__class__.__name__) + received_type_names.append(arg.type_name()) received_args_str = f"({', '.join([name for name in received_type_names])})" @@ -224,7 +240,7 @@ class BuiltInFunction(Function): def validate_args(self) -> "BuiltInFunction": if not self.input_spec.is_compatible(input_args=self.args): raise IncompatibleFunctionArguments( - f"Invalid arguments passed to function {self.name}.\n" + f"Incompatible arguments passed to function {self.name}.\n" f"{self._expected_received_error_msg()}" ) return self diff --git a/src/ytdl_sub/script/types/map.py b/src/ytdl_sub/script/types/map.py index b29c91a2..c0a5872a 100644 --- a/src/ytdl_sub/script/types/map.py +++ b/src/ytdl_sub/script/types/map.py @@ -21,7 +21,7 @@ class Map(NonHashable): value: Dict[Hashable, Resolvable] @classmethod - def human_readable_name(cls) -> str: + def type_name(cls) -> str: return "Map" diff --git a/src/ytdl_sub/script/types/resolvable.py b/src/ytdl_sub/script/types/resolvable.py index 463eb5e3..7c22c578 100644 --- a/src/ytdl_sub/script/types/resolvable.py +++ b/src/ytdl_sub/script/types/resolvable.py @@ -10,25 +10,27 @@ T = TypeVar("T") NumericT = TypeVar("NumericT", bound=int | float) -class ArgumentType(ABC): +class NamedType(ABC): + @classmethod + def type_name(cls) -> str: + return cls.__name__ + + +class ArgumentType(NamedType, ABC): """ Any possible argument type that has not been resolved yet """ - @classmethod - def human_readable_name(cls) -> str: - return cls.__name__ - -class AnyType_0(ABC): +class AnyType_0(NamedType, ABC): pass -class AnyType_1(ABC): +class AnyType_1(NamedType, ABC): pass -class AnyType_2(ABC): +class AnyType_2(NamedType, ABC): pass diff --git a/tests/unit/script/types/test_function.py b/tests/unit/script/types/test_function.py index 8c6e3bc1..1fa41238 100644 --- a/tests/unit/script/types/test_function.py +++ b/tests/unit/script/types/test_function.py @@ -74,7 +74,8 @@ class TestFunction: with pytest.raises( IncompatibleFunctionArguments, match=_incompatible_arguments_match( - expected="Map, Hashable, Optional", recieved="%if(...)->Union[Map, Array], String" + expected="Map, Hashable, Optional[AnyType]", + recieved="%if(...)->Union[Map, Array], String", ), ): Script({"func": function_str}).resolve() @@ -83,4 +84,8 @@ class TestFunction: "function_str", ["{%array_at({'a': 'dict?'}, 1)}" "{%array_extend('not', 'array')}"] ) def test_incompatible_types(self, function_str): - assert Script({"func": function_str}).resolve() + with pytest.raises( + IncompatibleFunctionArguments, + match=_incompatible_arguments_match(expected="Array, Integer", recieved="Map, Integer"), + ): + Script({"func": function_str}).resolve()