From 418f36b31e2d1e4527eec998065e76312bc4d2de Mon Sep 17 00:00:00 2001 From: Hironori Yamamoto Date: Thu, 1 Aug 2024 18:30:30 +0900 Subject: [PATCH] chore: add tests for gokart parameters (#389) * chore: add tests for gokart parameters * fix: handle parameters of `gokart.TaskOnKart` --- gokart/mypy.py | 18 ++++++++++++------ test/test_mypy.py | 15 ++++++++++++--- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/gokart/mypy.py b/gokart/mypy.py index 109a2fad..82b37716 100644 --- a/gokart/mypy.py +++ b/gokart/mypy.py @@ -60,9 +60,11 @@ class TaskOnKartPlugin(Plugin): def get_base_class_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None: - if 'gokart.task.luigi.Task' in fullname: - # gather attibutes from gokart.TaskOnKart - # the transformation does not affect because the class has `__init__` method + # The following gathers attributes from gokart.TaskOnKart such as `workspace_directory` + # the transformation does not affect because the class has `__init__` method of `gokart.TaskOnKart`. + # + # NOTE: `gokart.task.luigi.Task` condition is required for the release of luigi versions without py.typed + if fullname in {'gokart.task.luigi.Task', 'luigi.task.Task'}: return self._task_on_kart_class_maker_callback sym = self.lookup_fully_qualified(fullname) @@ -209,7 +211,6 @@ def transform(self) -> bool: if ('__init__' not in info.names or info.names['__init__'].plugin_generated) and attributes: args = [attr.to_argument(info, of='__init__') for attr in attributes] add_method_to_class(self._api, self._cls, '__init__', args=args, return_type=NoneType()) - info.metadata[METADATA_TAG] = { 'attributes': [attr.serialize() for attr in attributes], } @@ -330,6 +331,7 @@ def collect_attributes(self) -> Optional[list[TaskOnKartAttribute]]: info=cls.info, api=self._api, ) + return list(found_attrs.values()) def _collect_parameter_args(self, expr: Expression) -> tuple[bool, dict[str, Expression]]: @@ -404,9 +406,13 @@ def is_parameter_call(expr: Expression) -> bool: type_info = callee.node if type_info is None and isinstance(callee.expr, NameExpr): return PARAMETER_FULLNAME_MATCHER.match(f'{callee.expr.name}.{callee.name}') is not None - if isinstance(type_info, TypeInfo) and PARAMETER_FULLNAME_MATCHER.match(type_info.fullname): - return True + elif isinstance(callee, NameExpr): + type_info = callee.node + else: return False + + if isinstance(type_info, TypeInfo): + return PARAMETER_FULLNAME_MATCHER.match(type_info.fullname) is not None return False diff --git a/test/test_mypy.py b/test/test_mypy.py index 36b1f718..fabfcb6e 100644 --- a/test/test_mypy.py +++ b/test/test_mypy.py @@ -17,8 +17,12 @@ class MyTask(gokart.TaskOnKart): # NOTE: mypy shows attr-defined error for the following lines, so we need to ignore it. foo: int = luigi.IntParameter() # type: ignore bar: str = luigi.Parameter() # type: ignore + baz: bool = gokart.ExplicitBoolParameter() -MyTask(foo=1, bar='bar') + +# TaskOnKart parameters: +# - `complete_check_at_run` +MyTask(foo=1, bar='bar', baz=False, complete_check_at_run=False) """ with tempfile.NamedTemporaryFile(suffix='.py') as test_file: @@ -37,10 +41,13 @@ class MyTask(gokart.TaskOnKart): # NOTE: mypy shows attr-defined error for the following lines, so we need to ignore it. foo: int = luigi.IntParameter() # type: ignore bar: str = luigi.Parameter() # type: ignore + baz: bool = gokart.ExplicitBoolParameter() # issue: foo is int # not issue: bar is missing, because it can be set by config file. -MyTask(foo='1') +# TaskOnKart parameters: +# - `complete_check_at_run` +MyTask(foo='1', baz='not bool', complete_check_at_run='not bool') """ with tempfile.NamedTemporaryFile(suffix='.py') as test_file: @@ -48,4 +55,6 @@ class MyTask(gokart.TaskOnKart): test_file.flush() result = api.run(['--no-incremental', '--cache-dir=/dev/null', '--config-file', str(PYPROJECT_TOML), test_file.name]) self.assertIn('error: Argument "foo" to "MyTask" has incompatible type "str"; expected "int" [arg-type]', result[0]) - self.assertIn('Found 1 error in 1 file (checked 1 source file)', result[0]) + self.assertIn('error: Argument "baz" to "MyTask" has incompatible type "str"; expected "bool" [arg-type]', result[0]) + self.assertIn('error: Argument "complete_check_at_run" to "MyTask" has incompatible type "str"; expected "bool" [arg-type]', result[0]) + self.assertIn('Found 3 errors in 1 file (checked 1 source file)', result[0])