Skip to content

Commit c409d4a

Browse files
craigbarrattraman325
authored andcommitted
add global_ctx optional parameter to pyscript.reload service; see #63
1 parent a065403 commit c409d4a

File tree

6 files changed

+56
-14
lines changed

6 files changed

+56
-14
lines changed

custom_components/pyscript/__init__.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,16 @@ async def update_yaml_config(hass, config_entry):
9191
await hass.config_entries.flow.async_init(DOMAIN, context={"source": SOURCE_IMPORT}, data=config)
9292

9393

94-
def start_global_contexts():
94+
def start_global_contexts(global_ctx_only=None):
9595
"""Start all the file and apps global contexts."""
9696
start_list = []
9797
for global_ctx_name, global_ctx in GlobalContextMgr.items():
9898
idx = global_ctx_name.find(".")
9999
if idx < 0 or global_ctx_name[0:idx] not in {"file", "apps"}:
100100
continue
101+
if global_ctx_only is not None:
102+
if global_ctx_name != global_ctx_only and not global_ctx_name.startswith(global_ctx_only + "."):
103+
continue
101104
global_ctx.set_auto_start(True)
102105
start_list.append(global_ctx)
103106
for global_ctx in start_list:
@@ -139,11 +142,17 @@ async def reload_scripts_handler(call):
139142
await update_yaml_config(hass, config_entry)
140143
State.set_pyscript_config(config_entry.data)
141144

142-
await unload_scripts()
145+
global_ctx_only = call.data.get("global_ctx", None)
143146

144-
await load_scripts(hass, config_entry.data)
147+
if global_ctx_only is not None and not GlobalContextMgr.get(global_ctx_only):
148+
_LOGGER.error("pyscript.reload: no global context '%s' to reload", global_ctx_only)
149+
return
145150

146-
start_global_contexts()
151+
await unload_scripts(global_ctx_only=global_ctx_only)
152+
153+
await load_scripts(hass, config_entry.data, global_ctx_only=global_ctx_only)
154+
155+
start_global_contexts(global_ctx_only=global_ctx_only)
147156

148157
hass.services.async_register(DOMAIN, SERVICE_RELOAD, reload_scripts_handler)
149158

@@ -224,22 +233,25 @@ async def async_unload_entry(hass, config_entry):
224233
return True
225234

226235

227-
async def unload_scripts(unload_all=False):
236+
async def unload_scripts(global_ctx_only=None, unload_all=False):
228237
"""Unload all scripts from GlobalContextMgr with given name prefixes."""
229238
ctx_delete = {}
230239
for global_ctx_name, global_ctx in GlobalContextMgr.items():
231240
if not unload_all:
232241
idx = global_ctx_name.find(".")
233242
if idx < 0 or global_ctx_name[0:idx] not in {"file", "apps", "modules"}:
234243
continue
244+
if global_ctx_only is not None:
245+
if global_ctx_name != global_ctx_only and not global_ctx_name.startswith(global_ctx_only + "."):
246+
continue
235247
global_ctx.stop()
236248
ctx_delete[global_ctx_name] = global_ctx
237249
for global_ctx_name, global_ctx in ctx_delete.items():
238250
await GlobalContextMgr.delete(global_ctx_name)
239251

240252

241253
@bind_hass
242-
async def load_scripts(hass, data):
254+
async def load_scripts(hass, data, global_ctx_only=None):
243255
"""Load all python scripts in FOLDER."""
244256

245257
pyscript_dir = hass.config.path(FOLDER)
@@ -279,6 +291,9 @@ def glob_files(load_paths, data):
279291

280292
source_files = await hass.async_add_executor_job(glob_files, load_paths, data)
281293
for global_ctx_name, source_file, rel_import_path, fq_mod_name in source_files:
294+
if global_ctx_only is not None:
295+
if global_ctx_name != global_ctx_only and not global_ctx_name.startswith(global_ctx_only + "."):
296+
continue
282297
global_ctx = GlobalContext(
283298
global_ctx_name,
284299
global_sym_table={"__name__": fq_mod_name},

custom_components/pyscript/logbook.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"""Describe logbook events."""
2+
import logging
3+
24
from homeassistant.core import callback
35

46
from .const import DOMAIN
57

6-
import logging
7-
88
_LOGGER = logging.getLogger(__name__)
99

1010

@@ -42,4 +42,4 @@ def async_describe_logbook_event(event): # type: ignore
4242
"entity_id": ev_entity_id,
4343
}
4444

45-
async_describe_event(DOMAIN, "pyscript_running", async_describe_logbook_event)
45+
async_describe_event(DOMAIN, "pyscript_running", async_describe_logbook_event)

custom_components/pyscript/services.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
reload:
44
description: Reload all available pyscripts and restart triggers
5+
fields:
6+
global_ctx:
7+
description: only reload this specific global context (file or app)
8+
example: file.example
59

610
jupyter_kernel_start:
711
description: Start a jupyter kernel for interactive use; called by Jupyter front end

custom_components/pyscript/trigger.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010

1111
from croniter import croniter
1212

13-
import homeassistant.helpers.sun as sun
1413
from homeassistant.core import Context
14+
import homeassistant.helpers.sun as sun
1515

1616
from .const import LOGGER_PATH
1717
from .eval import AstEval

docs/reference.rst

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,7 @@ level will not appear in the log. Each log message function uses a log name of t
663663
664664
where ``FUNCNAME`` is the name of the top-level Python function (e.g., the one called by a trigger
665665
or service), defined in the script file ``FILENAME.py``. See the `Global Context <#global-context>`__
666-
section to see the logging path for other cases.
666+
section for the logging paths for other cases.
667667

668668
That allows you to set the log level for each Python top-level function separately if necessary.
669669
That setting also applies to any other Python functions that the top-level Python function calls.
@@ -919,8 +919,16 @@ Without Jupyter, the pyscript workflow involves editing scripts in the ``<config
919919
and calling the ``pyscript.reload`` service to reload the code. You will need to look at the log
920920
file for error messages (eg, syntax errors), or log output from your code.
921921

922-
A much better alternative is to use Jupyter notebook to interactively deveop and test functions,
923-
triggers and services.
922+
The ``pyscript.reload`` service takes an optional parameter ``global_ctx`` which specifies the name
923+
of a specific global context to reload; all others are unaffected. For example, specifying
924+
``file.example`` will just reload ``example.py``, and ``apps.my_app1`` will just reload
925+
``apps/my_app1.py`` or ``apps/my_app1/__init__.py``. See the `Global Context <#global-context>`__
926+
section for the mapping of file or app names to global context names. Specifying ``global_ctx``
927+
makes it convenient to just reload the script file or application you are developing without
928+
affecting the others.
929+
930+
A much better alternative to repeatedly modifying a script file and reloading it is to use Jupyter
931+
notebook to interactively deveop and test functions, triggers and services.
924932

925933
Jupyter auto-completion (with `<TAB>`) is supported in Jupyter notebook, console and lab. It should
926934
work after you have typed at least the first character. After you hit `<TAB>` you should see a list

tests/test_init.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,22 @@ def func5(var_name=None, value=None):
447447
), patch(
448448
"homeassistant.config.load_yaml_config_file", return_value={}
449449
):
450-
await hass.services.async_call("pyscript", "reload", {}, blocking=True)
450+
reload_param = {}
451+
if i % 2 == 1:
452+
#
453+
# on alternate times, just reload the specific file we are testing with
454+
#
455+
reload_param = {"global_ctx": "file.hello"}
456+
await hass.services.async_call("pyscript", "reload", reload_param, blocking=True)
457+
if i % 3 == 0:
458+
#
459+
# reload a file that doesn't exist; will log error and do nothing
460+
#
461+
await hass.services.async_call(
462+
"pyscript", "reload", {"global_ctx": "file.nosuchfile"}, blocking=True
463+
)
464+
465+
assert "pyscript.reload: no global context 'file.nosuchfile' to reload" in caplog.text
451466

452467

453468
async def test_misc_errors(hass, caplog):

0 commit comments

Comments
 (0)