Skip to content

Commit cbb2f44

Browse files
authored
Merge pull request #771 from dmamelin/feature/stub-generator
Add IDE stubs generator service, docs, and tests
2 parents 86ad86e + 75769e3 commit cbb2f44

File tree

9 files changed

+1231
-0
lines changed

9 files changed

+1231
-0
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ Pyscript also provides a kernel that interfaces with the Jupyter front-ends (eg,
1919
lab and VSCode). That allows you to develop and test pyscript code interactively. Plus you can interact
2020
with much of HASS by looking at state variables, calling services etc.
2121

22+
Pyscript can also generate IDE stub modules by calling the `pyscript.generate_stubs` service.
23+
See the “IDE Helpers” section of the docs for setup details.
24+
2225
## Documentation
2326

2427
Here is the [pyscript documentation](https://hacs-pyscript.readthedocs.io/en/stable).

custom_components/pyscript/__init__.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import json
66
import logging
77
import os
8+
import shutil
89
import time
910
import traceback
1011
from typing import Any, Callable, Dict, List, Set, Union
@@ -37,7 +38,9 @@
3738
FOLDER,
3839
LOGGER_PATH,
3940
REQUIREMENTS_FILE,
41+
SERVICE_GENERATE_STUBS,
4042
SERVICE_JUPYTER_KERNEL_START,
43+
SERVICE_RESPONSE_ONLY,
4144
UNSUB_LISTENERS,
4245
WATCHDOG_TASK,
4346
)
@@ -49,6 +52,7 @@
4952
from .mqtt import Mqtt
5053
from .requirements import install_requirements
5154
from .state import State, StateVal
55+
from .stubs.generator import StubsGenerator
5256
from .trigger import TrigTime
5357
from .webhook import Webhook
5458

@@ -300,6 +304,47 @@ async def reload_scripts_handler(call: ServiceCall) -> None:
300304

301305
hass.services.async_register(DOMAIN, SERVICE_RELOAD, reload_scripts_handler)
302306

307+
async def generate_stubs_service(call: ServiceCall) -> Dict[str, Any]:
308+
"""Generate pyscript IDE stub files."""
309+
310+
generator = StubsGenerator(hass)
311+
generated_body = await generator.build()
312+
stubs_path = os.path.join(hass.config.path(FOLDER), "modules", "stubs")
313+
314+
def write_stubs(path) -> dict[str, Any]:
315+
res: dict[str, Any] = {}
316+
try:
317+
os.makedirs(path, exist_ok=True)
318+
319+
builtins_path = os.path.join(os.path.dirname(__file__), "stubs", "pyscript_builtins.py")
320+
shutil.copy2(builtins_path, path)
321+
322+
gen_path = os.path.join(path, "pyscript_generated.py")
323+
with open(gen_path, "w", encoding="utf-8") as f:
324+
f.write(generated_body)
325+
res["status"] = "OK"
326+
return res
327+
except Exception as e:
328+
_LOGGER.exception("Stubs generation failed: %s", e)
329+
res["status"] = "Error"
330+
res["exception"] = str(e)
331+
res["message"] = "Check pyscript logs"
332+
return res
333+
334+
result = await hass.async_add_executor_job(write_stubs, stubs_path)
335+
336+
if generator.ignored_identifiers:
337+
result["ignored_identifiers"] = sorted(generator.ignored_identifiers)
338+
339+
if result["status"] == "OK":
340+
_LOGGER.info("Pyscript stubs generated to %s", stubs_path)
341+
342+
return result
343+
344+
hass.services.async_register(
345+
DOMAIN, SERVICE_GENERATE_STUBS, generate_stubs_service, supports_response=SERVICE_RESPONSE_ONLY
346+
)
347+
303348
async def jupyter_kernel_start(call: ServiceCall) -> None:
304349
"""Handle Jupyter kernel start call."""
305350
_LOGGER.debug("service call to jupyter_kernel_start: %s", call.data)

custom_components/pyscript/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
CONF_INSTALLED_PACKAGES = "_installed_packages"
3535

3636
SERVICE_JUPYTER_KERNEL_START = "jupyter_kernel_start"
37+
SERVICE_GENERATE_STUBS = "generate_stubs"
3738

3839
LOGGER_PATH = "custom_components.pyscript"
3940

custom_components/pyscript/eval.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,14 @@ async def ast_importfrom(self, arg):
999999
raise ModuleNotFoundError(f"module '{imp.name}' not found")
10001000
self.sym_table[imp.name if imp.asname is None else imp.asname] = mod
10011001
return
1002+
if arg.module == "stubs" or arg.module.startswith("stubs."):
1003+
for imp in arg.names:
1004+
if imp.asname is not None:
1005+
raise ModuleNotFoundError(
1006+
f"from {arg.module} import {imp.name} *as {imp.asname}* not supported for stubs"
1007+
)
1008+
_LOGGER.debug("Skipping stubs import %s", arg.module)
1009+
return
10021010
mod, error_ctx = await self.global_ctx.module_import(arg.module, arg.level)
10031011
if error_ctx:
10041012
self.exception_obj = error_ctx.exception_obj

custom_components/pyscript/services.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,7 @@ jupyter_kernel_start:
105105
default: pyscript
106106
selector:
107107
text:
108+
109+
generate_stubs:
110+
name: Generate pyscript stubs
111+
description: Build a stub files combining builtin helpers with discovered entities and services.

0 commit comments

Comments
 (0)