Skip to content

Commit 6525af1

Browse files
authored
Merge pull request #804 from dmamelin/tech-debt
Technical: typing and cleanup
2 parents 52d4cac + 901df9f commit 6525af1

14 files changed

Lines changed: 169 additions & 187 deletions

custom_components/pyscript/__init__.py

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
"""Component to allow running Python scripts."""
22

33
import asyncio
4+
from collections.abc import Awaitable, Callable
45
import glob
56
import json
67
import logging
78
import os
89
import shutil
910
import time
1011
import traceback
11-
from typing import Any, Callable, Dict, List, Set, Union
12+
from typing import Any
1213

1314
import voluptuous as vol
1415
from watchdog.events import DirModifiedEvent, FileSystemEvent, FileSystemEventHandler
@@ -22,7 +23,7 @@
2223
EVENT_STATE_CHANGED,
2324
SERVICE_RELOAD,
2425
)
25-
from homeassistant.core import Event as HAEvent, HomeAssistant, ServiceCall
26+
from homeassistant.core import Event as HAEvent, HomeAssistant, ServiceCall, SupportsResponse
2627
from homeassistant.exceptions import HomeAssistantError
2728
import homeassistant.helpers.config_validation as cv
2829
from homeassistant.helpers.restore_state import DATA_RESTORE_STATE
@@ -40,7 +41,6 @@
4041
REQUIREMENTS_FILE,
4142
SERVICE_GENERATE_STUBS,
4243
SERVICE_JUPYTER_KERNEL_START,
43-
SERVICE_RESPONSE_ONLY,
4444
UNSUB_LISTENERS,
4545
WATCHDOG_TASK,
4646
)
@@ -98,7 +98,7 @@ async def update_yaml_config(hass: HomeAssistant, config_entry: ConfigEntry) ->
9898
conf = await async_hass_config_yaml(hass)
9999
except HomeAssistantError as err:
100100
_LOGGER.error(err)
101-
return
101+
return False
102102

103103
config = PYSCRIPT_SCHEMA(conf.get(DOMAIN, {}))
104104

@@ -128,7 +128,7 @@ async def update_yaml_config(hass: HomeAssistant, config_entry: ConfigEntry) ->
128128
return False
129129

130130

131-
def start_global_contexts(global_ctx_only: str = None) -> None:
131+
def start_global_contexts(global_ctx_only: str | None = None) -> None:
132132
"""Start all the file and apps global contexts."""
133133
start_list = []
134134
for global_ctx_name, global_ctx in GlobalContextMgr.items():
@@ -145,7 +145,9 @@ def start_global_contexts(global_ctx_only: str = None) -> None:
145145

146146

147147
async def watchdog_start(
148-
hass: HomeAssistant, pyscript_folder: str, reload_scripts_handler: Callable[[None], None]
148+
hass: HomeAssistant,
149+
pyscript_folder: str,
150+
reload_scripts_handler: Callable[[ServiceCall], Awaitable[None]],
149151
) -> None:
150152
"""Start watchdog thread to look for changed files in pyscript_folder."""
151153
if WATCHDOG_TASK in hass.data[DOMAIN]:
@@ -201,7 +203,7 @@ def on_deleted(self, event: FileSystemEvent) -> None:
201203
self.process(event)
202204

203205
async def task_watchdog(watchdog_q: asyncio.Queue) -> None:
204-
def check_event(event, do_reload: bool) -> bool:
206+
def check_event(event: FileSystemEvent, do_reload: bool) -> bool:
205207
"""Check if event should trigger a reload."""
206208
if event.is_directory:
207209
# don't reload if it's just a directory modified
@@ -230,7 +232,7 @@ def check_event(event, do_reload: bool) -> bool:
230232
do_reload = check_event(
231233
await asyncio.wait_for(watchdog_q.get(), timeout=0.05), do_reload
232234
)
233-
except asyncio.TimeoutError:
235+
except TimeoutError:
234236
break
235237
if do_reload:
236238
await reload_scripts_handler(None)
@@ -304,14 +306,14 @@ async def reload_scripts_handler(call: ServiceCall) -> None:
304306

305307
hass.services.async_register(DOMAIN, SERVICE_RELOAD, reload_scripts_handler)
306308

307-
async def generate_stubs_service(call: ServiceCall) -> Dict[str, Any]:
309+
async def generate_stubs_service(call: ServiceCall) -> dict[str, Any]:
308310
"""Generate pyscript IDE stub files."""
309311

310312
generator = StubsGenerator(hass)
311313
generated_body = await generator.build()
312314
stubs_path = os.path.join(hass.config.path(FOLDER), "modules", "stubs")
313315

314-
def write_stubs(path) -> dict[str, Any]:
316+
def write_stubs(path: str) -> dict[str, Any]:
315317
res: dict[str, Any] = {}
316318
try:
317319
os.makedirs(path, exist_ok=True)
@@ -342,7 +344,7 @@ def write_stubs(path) -> dict[str, Any]:
342344
return result
343345

344346
hass.services.async_register(
345-
DOMAIN, SERVICE_GENERATE_STUBS, generate_stubs_service, supports_response=SERVICE_RESPONSE_ONLY
347+
DOMAIN, SERVICE_GENERATE_STUBS, generate_stubs_service, supports_response=SupportsResponse.ONLY
346348
)
347349

348350
async def jupyter_kernel_start(call: ServiceCall) -> None:
@@ -443,7 +445,7 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
443445
return True
444446

445447

446-
async def unload_scripts(global_ctx_only: str = None, unload_all: bool = False) -> None:
448+
async def unload_scripts(global_ctx_only: str | None = None, unload_all: bool = False) -> None:
447449
"""Unload all scripts from GlobalContextMgr with given name prefixes."""
448450
ctx_delete = {}
449451
for global_ctx_name, global_ctx in GlobalContextMgr.items():
@@ -462,7 +464,9 @@ async def unload_scripts(global_ctx_only: str = None, unload_all: bool = False)
462464

463465

464466
@bind_hass
465-
async def load_scripts(hass: HomeAssistant, config_data: Dict[str, Any], global_ctx_only: str = None):
467+
async def load_scripts(
468+
hass: HomeAssistant, config_data: dict[str, Any], global_ctx_only: str | None = None
469+
) -> None:
466470
"""Load all python scripts in FOLDER."""
467471

468472
class SourceFile:
@@ -496,8 +500,8 @@ def __init__(
496500
pyscript_dir = hass.config.path(FOLDER)
497501

498502
def glob_read_files(
499-
load_paths: List[Set[Union[str, bool]]], apps_config: Dict[str, Any]
500-
) -> Dict[str, SourceFile]:
503+
load_paths: list[tuple[str, str, bool, bool]], apps_config: dict[str, Any]
504+
) -> dict[str, SourceFile]:
501505
"""Expand globs and read all the source files."""
502506
ctx2source = {}
503507
for path, match, check_config, autoload in load_paths:

custom_components/pyscript/const.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,5 @@
11
"""Define pyscript-wide constants."""
22

3-
#
4-
# 2023.7 supports service response; handle older versions by defaulting enum
5-
# Should eventually deprecate this and just use SupportsResponse import
6-
#
7-
try:
8-
from homeassistant.core import SupportsResponse
9-
10-
SERVICE_RESPONSE_NONE = SupportsResponse.NONE
11-
SERVICE_RESPONSE_OPTIONAL = SupportsResponse.OPTIONAL
12-
SERVICE_RESPONSE_ONLY = SupportsResponse.ONLY
13-
except ImportError:
14-
SERVICE_RESPONSE_NONE = None
15-
SERVICE_RESPONSE_OPTIONAL = None
16-
SERVICE_RESPONSE_ONLY = None
17-
183
DOMAIN = "pyscript"
194

205
CONFIG_ENTRY = "config_entry"

custom_components/pyscript/eval.py

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
1313
import sys
1414
import time
1515
import traceback
16+
from typing import TYPE_CHECKING, Any
1617
import weakref
1718

1819
import yaml
1920

2021
from homeassistant.const import SERVICE_RELOAD
22+
from homeassistant.core import SupportsResponse
2123
from homeassistant.helpers.service import async_set_service_schema
2224

2325
from .const import (
@@ -27,11 +29,15 @@
2729
DOMAIN,
2830
LOGGER_PATH,
2931
SERVICE_JUPYTER_KERNEL_START,
30-
SERVICE_RESPONSE_NONE,
3132
)
3233
from .function import Function
3334
from .state import State
3435

36+
if TYPE_CHECKING:
37+
from .global_ctx import GlobalContext
38+
39+
type SymTable = dict[str, Any]
40+
3541
_LOGGER = logging.getLogger(LOGGER_PATH + ".eval")
3642

3743
#
@@ -315,7 +321,14 @@ def getattr(self):
315321
class EvalFunc:
316322
"""Class for a callable pyscript function."""
317323

318-
def __init__(self, func_def, code_list, code_str, global_ctx, async_func=False):
324+
def __init__(
325+
self,
326+
func_def: ast.FunctionDef,
327+
code_list: list[str],
328+
code_str: str,
329+
global_ctx: "GlobalContext",
330+
async_func: bool = False,
331+
) -> None:
319332
"""Initialize a function calling context."""
320333
self.func_def = func_def
321334
self.name = func_def.name
@@ -533,7 +546,7 @@ async def do_service_call(func, ast_ctx, data):
533546
domain,
534547
name,
535548
pyscript_service_factory(func_name, self),
536-
dec_kwargs.get("supports_response", SERVICE_RESPONSE_NONE),
549+
dec_kwargs.get("supports_response", SupportsResponse.NONE),
537550
)
538551
async_set_service_schema(Function.hass, domain, name, service_desc)
539552
self.trigger_service.add(srv_name)
@@ -838,12 +851,12 @@ async def check_for_closure(self, arg):
838851
class EvalFuncVar:
839852
"""Class for a callable pyscript function."""
840853

841-
def __init__(self, func):
854+
def __init__(self, func: EvalFunc) -> None:
842855
"""Initialize instance with given EvalFunc function."""
843856
self.func = func
844-
self.ast_ctx = None
857+
self.ast_ctx: AstEval | None = None
845858

846-
def get_func(self):
859+
def get_func(self) -> EvalFunc:
847860
"""Return the EvalFunc function."""
848861
return self.func
849862

@@ -895,7 +908,7 @@ async def __call__(self, *args, **kwargs):
895908
class EvalFuncVarClassInst(EvalFuncVar):
896909
"""Class for a callable pyscript class instance function."""
897910

898-
def __init__(self, func, ast_ctx, class_inst_weak):
911+
def __init__(self, func: EvalFunc, ast_ctx: "AstEval", class_inst_weak: weakref.ReferenceType) -> None:
899912
"""Initialize instance with given EvalFunc function."""
900913
super().__init__(func)
901914
self.ast_ctx = ast_ctx
@@ -913,25 +926,25 @@ async def __call__(self, *args, **kwargs):
913926
class AstEval:
914927
"""Python interpreter AST object evaluator."""
915928

916-
def __init__(self, name, global_ctx, logger_name=None):
929+
def __init__(self, name: str, global_ctx: "GlobalContext", logger_name: str | None = None) -> None:
917930
"""Initialize an interpreter execution context."""
918931
self.name = name
919932
self.str = None
920933
self.ast = None
921934
self.global_ctx = global_ctx
922-
self.global_sym_table = global_ctx.get_global_sym_table() if global_ctx else {}
923-
self.sym_table_stack = []
935+
self.global_sym_table: SymTable = global_ctx.get_global_sym_table() if global_ctx else {}
936+
self.sym_table_stack: list[SymTable] = []
924937
self.sym_table = self.global_sym_table
925-
self.local_sym_table = {}
926-
self.user_locals = {}
927-
self.curr_func = None
938+
self.local_sym_table: SymTable = {}
939+
self.user_locals: SymTable = {}
940+
self.curr_func: EvalFunc | None = None
928941
self.filename = name
929-
self.code_str = None
930-
self.code_list = None
931-
self.exception = None
932-
self.exception_obj = None
933-
self.exception_long = None
934-
self.exception_curr = None
942+
self.code_str: str | None = None
943+
self.code_list: list[str] | None = None
944+
self.exception: str | None = None
945+
self.exception_obj: Exception | None = None
946+
self.exception_long: str | None = None
947+
self.exception_curr: Exception | None = None
935948
self.lineno = 1
936949
self.col_offset = 0
937950
self.logger_handlers = set()
@@ -2159,7 +2172,7 @@ async def get_names(self, this_ast=None, nonlocal_names=None, global_names=None,
21592172
await self.get_names_set(this_ast, names, nonlocal_names, global_names, local_names)
21602173
return names
21612174

2162-
def parse(self, code_str, filename=None, mode="exec"):
2175+
def parse(self, code_str: str, filename: str | None = None, mode: str = "exec") -> bool:
21632176
"""Parse the code_str source code into an AST tree."""
21642177
self.exception = None
21652178
self.exception_obj = None
@@ -2298,7 +2311,7 @@ def completions(self, root):
22982311
for attr in var.__dict__:
22992312
if attr.lower().startswith(attr_root) and (attr_root != "" or attr[0:1] != "_"):
23002313
words.add(f"{name}.{attr}")
2301-
except Exception:
2314+
except Exception: # noqa: S110
23022315
pass
23032316
for keyw in set(keyword.kwlist) - {"yield"}:
23042317
if keyw.lower().startswith(root):
@@ -2313,7 +2326,7 @@ def completions(self, root):
23132326
words.add(name)
23142327
return words
23152328

2316-
async def eval(self, new_state_vars=None, merge_local=False):
2329+
async def eval(self, new_state_vars: dict[str, Any] | None = None, merge_local: bool = False) -> None:
23172330
"""Execute parsed code, with the optional state variables added to the scope."""
23182331
self.exception = None
23192332
self.exception_obj = None

0 commit comments

Comments
 (0)