Skip to content

Commit 8dbde8b

Browse files
committed
binding of class instance methods to ast context done after instance creation
1 parent 5ac1935 commit 8dbde8b

File tree

4 files changed

+42
-30
lines changed

4 files changed

+42
-30
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ __pycache__
22
icon
33
venv
44
hass-custom-pyscript.zip
5+
docs/_build
56
.coverage
67
.vscode
78
.*.swp

custom_components/pyscript/eval.py

+36-26
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,10 @@ def __getattr__(self, attr):
188188
raise NameError(f"name '{self.name}' is not defined")
189189
return getattr(self.value, attr)
190190

191+
def __repr__(self):
192+
"""Generate string with address and value."""
193+
return f"EvalLocalVar @{hex(id(self))} = {self.value if self.defined else 'undefined'}"
194+
191195

192196
class EvalName:
193197
"""Identifier that hasn't yet been resolved."""
@@ -350,7 +354,7 @@ async def pyscript_service_handler(call):
350354
func_args.update(call.data)
351355

352356
async def do_service_call(func, ast_ctx, data):
353-
await func.call(ast_ctx, [], call.data)
357+
await func.call(ast_ctx, **call.data)
354358
if ast_ctx.get_exception_obj():
355359
ast_ctx.get_logger().error(ast_ctx.get_exception_long())
356360

@@ -575,7 +579,7 @@ async def try_aeval(self, ast_ctx, arg):
575579
if ast_ctx.exception_long is None:
576580
ast_ctx.exception_long = ast_ctx.format_exc(err, arg.lineno, arg.col_offset)
577581

578-
async def call(self, ast_ctx, args=None, kwargs=None):
582+
async def call(self, ast_ctx, *args, **kwargs):
579583
"""Call the function with the given context and arguments."""
580584
sym_table = {}
581585
if args is None:
@@ -668,6 +672,19 @@ def __del__(self):
668672
self.func.trigger_stop()
669673

670674

675+
class EvalFuncVarClassInst(EvalFuncVar):
676+
"""Class for a callable pyscript class instance function."""
677+
678+
def __init__(self, func, class_inst):
679+
"""Initialize instance with given EvalFunc function."""
680+
super().__init__(func)
681+
self.class_inst = class_inst
682+
683+
def call(self, ctx, *args, **kwargs):
684+
"""Call the EvalFunc function."""
685+
return self.func.call(ctx, self.class_inst, *args, **kwargs)
686+
687+
671688
class AstEval:
672689
"""Python interpreter AST object evaluator."""
673690

@@ -858,19 +875,7 @@ async def ast_classdef(self, arg):
858875
raise SyntaxError(f"{val.name()} statement outside loop")
859876
self.sym_table = self.sym_table_stack.pop()
860877

861-
for name, func in sym_table.items():
862-
if not isinstance(func, EvalFuncVar):
863-
continue
864-
865-
def class_func_factory(func):
866-
async def class_func_wrapper(this_self, *args, **kwargs):
867-
method_args = [this_self, *args]
868-
return await func.call(self, method_args, kwargs)
869-
870-
return class_func_wrapper
871-
872-
sym_table[name] = class_func_factory(func.get_func())
873-
878+
sym_table["__init__evalfunc_wrap__"] = None
874879
if "__init__" in sym_table:
875880
sym_table["__init__evalfunc_wrap__"] = sym_table["__init__"]
876881
del sym_table["__init__"]
@@ -1017,7 +1022,7 @@ async def ast_with(self, arg, async_attr=""):
10171022
)
10181023
for ctx in ctx_list:
10191024
if ctx["target"]:
1020-
value = await self.call_func(ctx["enter"], enter_attr, [ctx["manager"]], {})
1025+
value = await self.call_func(ctx["enter"], enter_attr, ctx["manager"])
10211026
await self.recurse_assign(ctx["target"], value)
10221027
for arg1 in arg.body:
10231028
val = await self.aeval(arg1)
@@ -1027,14 +1032,14 @@ async def ast_with(self, arg, async_attr=""):
10271032
hit_except = True
10281033
exit_ok = True
10291034
for ctx in reversed(ctx_list):
1030-
ret = await self.call_func(ctx["exit"], exit_attr, [ctx["manager"], *sys.exc_info()], {})
1035+
ret = await self.call_func(ctx["exit"], exit_attr, ctx["manager"], *sys.exc_info())
10311036
exit_ok = exit_ok and ret
10321037
if not exit_ok:
10331038
raise
10341039
finally:
10351040
if not hit_except:
10361041
for ctx in reversed(ctx_list):
1037-
await self.call_func(ctx["exit"], exit_attr, [ctx["manager"], None, None, None], {})
1042+
await self.call_func(ctx["exit"], exit_attr, ctx["manager"], None, None, None)
10381043
return val
10391044

10401045
async def ast_asyncwith(self, arg):
@@ -1590,19 +1595,24 @@ async def ast_call(self, arg):
15901595
func_name = func.get_name()
15911596
func = func.get()
15921597
_LOGGER.debug("%s: calling %s(%s, %s)", self.name, func_name, arg_str, kwargs)
1593-
return await self.call_func(func, func_name, args, kwargs)
1598+
return await self.call_func(func, func_name, *args, **kwargs)
15941599

1595-
async def call_func(self, func, func_name, args, kwargs):
1600+
async def call_func(self, func, func_name, *args, **kwargs):
15961601
"""Call a function with the given arguments."""
15971602
if isinstance(func, EvalFuncVar):
1598-
return await func.get_func().call(self, args, kwargs)
1603+
return await func.call(self, *args, **kwargs)
15991604
if inspect.isclass(func) and hasattr(func, "__init__evalfunc_wrap__"):
1600-
#
1601-
# since our __init__ function is async, create the class instance
1602-
# without arguments and then call the async __init__evalfunc_wrap__
1603-
#
16041605
inst = func()
1605-
await inst.__init__evalfunc_wrap__(*args, **kwargs)
1606+
for name in inst.__dir__():
1607+
value = getattr(inst, name)
1608+
if type(value) is not EvalFuncVar:
1609+
continue
1610+
setattr(inst, name, EvalFuncVarClassInst(value.get_func(), inst))
1611+
if getattr(func, "__init__evalfunc_wrap__") is not None:
1612+
#
1613+
# since our __init__ function is async, call the renamed one
1614+
#
1615+
await inst.__init__evalfunc_wrap__.call(self, *args, **kwargs)
16061616
return inst
16071617
if asyncio.iscoroutinefunction(func):
16081618
return await func(*args, **kwargs)

custom_components/pyscript/trigger.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -534,10 +534,10 @@ async def trigger_watch(self):
534534

535535
try:
536536

537-
async def do_func_call(func, ast_ctx, task_unique, kwargs=None):
537+
async def do_func_call(func, ast_ctx, task_unique, **kwargs):
538538
if task_unique and self.task_unique_func:
539539
await self.task_unique_func(task_unique)
540-
await func.call(ast_ctx, kwargs=kwargs)
540+
await func.call(ast_ctx, **kwargs)
541541
if ast_ctx.get_exception_obj():
542542
ast_ctx.get_logger().error(ast_ctx.get_exception_long())
543543

@@ -656,7 +656,7 @@ async def do_func_call(func, ast_ctx, task_unique, kwargs=None):
656656
func_args,
657657
)
658658
Function.create_task(
659-
do_func_call(self.action, self.action_ast_ctx, self.task_unique, kwargs=func_args)
659+
do_func_call(self.action, self.action_ast_ctx, self.task_unique, **func_args)
660660
)
661661

662662
except asyncio.CancelledError:

pylintrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ disable=
5252
unused-argument,
5353
no-value-for-parameter,
5454
unsubscriptable-object,
55-
wrong-import-order
55+
wrong-import-order,
56+
unidiomatic-typecheck
5657
enable=
5758
use-symbolic-message-instead
5859

0 commit comments

Comments
 (0)