Skip to content

Commit 0799187

Browse files
authored
Merge pull request #39 from raman325/fix_config_flow
Fix access to config data now that we are using config flow
2 parents 3426454 + d4ddf31 commit 0799187

File tree

8 files changed

+132
-104
lines changed

8 files changed

+132
-104
lines changed

custom_components/pyscript/__init__.py

+13-10
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import voluptuous as vol
99

10-
from homeassistant.config import async_hass_config_yaml, async_process_component_config
10+
from homeassistant.config import async_hass_config_yaml
1111
from homeassistant.config_entries import SOURCE_IMPORT
1212
from homeassistant.const import (
1313
EVENT_HOMEASSISTANT_STARTED,
@@ -17,7 +17,7 @@
1717
)
1818
from homeassistant.exceptions import HomeAssistantError
1919
import homeassistant.helpers.config_validation as cv
20-
from homeassistant.loader import async_get_integration, bind_hass
20+
from homeassistant.loader import bind_hass
2121

2222
from .const import CONF_ALLOW_ALL_IMPORTS, DOMAIN, FOLDER, LOGGER_PATH, SERVICE_JUPYTER_KERNEL_START
2323
from .eval import AstEval
@@ -81,11 +81,14 @@ async def reload_scripts_handler(call):
8181
_LOGGER.error(err)
8282
return
8383

84-
integration = await async_get_integration(hass, DOMAIN)
84+
config = PYSCRIPT_SCHEMA(conf.get(DOMAIN, {}))
8585

86-
config = await async_process_component_config(hass, conf, integration)
86+
# If data in config doesn't match config entry, trigger a config import
87+
# so that the config entry can get updated
88+
if config != config_entry.data:
89+
await hass.config_entries.flow.async_init(DOMAIN, context={"source": SOURCE_IMPORT}, data=config)
8790

88-
State.set_pyscript_config(config.get(DOMAIN, {}))
91+
State.set_pyscript_config(config_entry.data)
8992

9093
ctx_delete = {}
9194
for global_ctx_name, global_ctx in GlobalContextMgr.items():
@@ -97,7 +100,7 @@ async def reload_scripts_handler(call):
97100
for global_ctx_name, global_ctx in ctx_delete.items():
98101
await GlobalContextMgr.delete(global_ctx_name)
99102

100-
await load_scripts(hass, config)
103+
await load_scripts(hass, config_entry.data)
101104

102105
for global_ctx_name, global_ctx in GlobalContextMgr.items():
103106
idx = global_ctx_name.find(".")
@@ -188,14 +191,14 @@ async def async_unload_entry(hass, config_entry):
188191

189192

190193
@bind_hass
191-
async def load_scripts(hass, config):
194+
async def load_scripts(hass, data):
192195
"""Load all python scripts in FOLDER."""
193196

194197
pyscript_dir = hass.config.path(FOLDER)
195198

196-
def glob_files(load_paths, config):
199+
def glob_files(load_paths, data):
197200
source_files = []
198-
apps_config = config.get(DOMAIN, {}).get("apps", None)
201+
apps_config = data.get("apps", None)
199202
for path, match, check_config in load_paths:
200203
for this_path in sorted(glob.glob(os.path.join(pyscript_dir, path, match))):
201204
rel_import_path = None
@@ -227,7 +230,7 @@ def glob_files(load_paths, config):
227230
["", "*.py", False],
228231
]
229232

230-
source_files = await hass.async_add_executor_job(glob_files, load_paths, config)
233+
source_files = await hass.async_add_executor_job(glob_files, load_paths, data)
231234
for global_ctx_name, source_file, rel_import_path, fq_mod_name in source_files:
232235
global_ctx = GlobalContext(
233236
global_ctx_name,

custom_components/pyscript/config_flow.py

+25-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"""Config flow for pyscript."""
2+
import json
23
from typing import Any, Dict
34

45
import voluptuous as vol
56

67
from homeassistant import config_entries
8+
from homeassistant.config_entries import SOURCE_IMPORT
79

810
from .const import CONF_ALLOW_ALL_IMPORTS, DOMAIN
911

@@ -18,7 +20,7 @@ class PyscriptConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
1820
VERSION = 1
1921
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH
2022

21-
async def async_step_user(self, user_input: Dict[str, Any] = None) -> None:
23+
async def async_step_user(self, user_input: Dict[str, Any] = None) -> Dict[str, Any]:
2224
"""Handle a flow initialized by the user."""
2325
if user_input is not None:
2426
if len(self.hass.config_entries.async_entries(DOMAIN)) > 0:
@@ -29,20 +31,35 @@ async def async_step_user(self, user_input: Dict[str, Any] = None) -> None:
2931

3032
return self.async_show_form(step_id="user", data_schema=PYSCRIPT_SCHEMA)
3133

32-
async def async_step_import(self, import_config: Dict[str, Any] = None) -> None:
34+
async def async_step_import(self, import_config: Dict[str, Any] = None) -> Dict[str, Any]:
3335
"""Import a config entry from configuration.yaml."""
36+
# Convert OrderedDict to dict
37+
import_config = json.loads(json.dumps(import_config))
38+
3439
# Check if import config entry matches any existing config entries
3540
# so we can update it if necessary
3641
entries = self.hass.config_entries.async_entries(DOMAIN)
3742
if entries:
3843
entry = entries[0]
39-
if entry.data.get(CONF_ALLOW_ALL_IMPORTS, False) != import_config.get(
40-
CONF_ALLOW_ALL_IMPORTS, False
41-
):
42-
updated_data = entry.data.copy()
43-
updated_data[CONF_ALLOW_ALL_IMPORTS] = import_config.get(CONF_ALLOW_ALL_IMPORTS, False)
44+
updated_data = entry.data.copy()
45+
46+
# Update values for all keys, excluding `allow_all_imports` for entries
47+
# set up through the UI.
48+
for k, v in import_config.items():
49+
if entry.source == SOURCE_IMPORT or k != CONF_ALLOW_ALL_IMPORTS:
50+
updated_data[k] = v
51+
52+
# Remove values for all keys in entry.data that are not in the imported config,
53+
# excluding `allow_all_imports` for entries set up through the UI.
54+
for key in entry.data:
55+
if (
56+
entry.source == SOURCE_IMPORT or key != CONF_ALLOW_ALL_IMPORTS
57+
) and key not in import_config:
58+
updated_data.pop(key)
59+
60+
# Update and reload entry if data needs to be updated
61+
if updated_data != entry.data:
4462
self.hass.config_entries.async_update_entry(entry=entry, data=updated_data)
45-
await self.hass.config_entries.async_reload(entry.entry_id)
4663
return self.async_abort(reason="updated_entry")
4764

4865
return self.async_abort(reason="already_configured_service")

tests/test_config_flow.py

+71-2
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ async def test_import_flow(hass, pyscript_bypass_setup):
7575
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
7676

7777

78-
async def test_import_flow_update_entry(hass):
79-
"""Test import config flow updates existing entry."""
78+
async def test_import_flow_update_allow_all_imports(hass):
79+
"""Test import config flow updates existing entry when `allow_all_imports` has changed."""
8080
result = await hass.config_entries.flow.async_init(
8181
DOMAIN, context={"source": SOURCE_IMPORT}, data=PYSCRIPT_SCHEMA({})
8282
)
@@ -91,6 +91,36 @@ async def test_import_flow_update_entry(hass):
9191
assert result["reason"] == "updated_entry"
9292

9393

94+
async def test_import_flow_update_apps_from_none(hass):
95+
"""Test import config flow updates existing entry when `apps` has changed from None to something."""
96+
result = await hass.config_entries.flow.async_init(
97+
DOMAIN, context={"source": SOURCE_IMPORT}, data=PYSCRIPT_SCHEMA({})
98+
)
99+
100+
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
101+
102+
result = await hass.config_entries.flow.async_init(
103+
DOMAIN, context={"source": SOURCE_IMPORT}, data={"apps": {"test_app": {"param": 1}}}
104+
)
105+
106+
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
107+
assert result["reason"] == "updated_entry"
108+
109+
110+
async def test_import_flow_update_apps_to_none(hass):
111+
"""Test import config flow updates existing entry when `apps` has changed from something to None."""
112+
result = await hass.config_entries.flow.async_init(
113+
DOMAIN, context={"source": SOURCE_IMPORT}, data=PYSCRIPT_SCHEMA({"apps": {"test_app": {"param": 1}}})
114+
)
115+
116+
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
117+
118+
result = await hass.config_entries.flow.async_init(DOMAIN, context={"source": SOURCE_IMPORT}, data={})
119+
120+
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
121+
assert result["reason"] == "updated_entry"
122+
123+
94124
async def test_import_flow_no_update(hass):
95125
"""Test import config flow doesn't update existing entry when data is same."""
96126
result = await hass.config_entries.flow.async_init(
@@ -105,3 +135,42 @@ async def test_import_flow_no_update(hass):
105135

106136
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
107137
assert result["reason"] == "already_configured_service"
138+
139+
140+
async def test_import_flow_update_user(hass):
141+
"""Test import config flow update excludes `allow_all_imports` from being updated when updated entry was a user entry."""
142+
result = await hass.config_entries.flow.async_init(
143+
DOMAIN, context={"source": SOURCE_USER}, data=PYSCRIPT_SCHEMA({CONF_ALLOW_ALL_IMPORTS: True})
144+
)
145+
146+
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
147+
148+
result = await hass.config_entries.flow.async_init(
149+
DOMAIN, context={"source": SOURCE_IMPORT}, data={"apps": {"test_app": {"param": 1}}}
150+
)
151+
152+
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
153+
assert result["reason"] == "updated_entry"
154+
155+
hass.config_entries.async_entries(DOMAIN)[0].data == {
156+
CONF_ALLOW_ALL_IMPORTS: True,
157+
"apps": {"test_app": {"param": 1}},
158+
}
159+
160+
161+
async def test_import_flow_update_import(hass):
162+
"""Test import config flow update includes `allow_all_imports` in update when updated entry was imported entry."""
163+
result = await hass.config_entries.flow.async_init(
164+
DOMAIN, context={"source": SOURCE_IMPORT}, data=PYSCRIPT_SCHEMA({CONF_ALLOW_ALL_IMPORTS: True})
165+
)
166+
167+
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
168+
169+
result = await hass.config_entries.flow.async_init(
170+
DOMAIN, context={"source": SOURCE_IMPORT}, data={"apps": {"test_app": {"param": 1}}}
171+
)
172+
173+
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
174+
assert result["reason"] == "updated_entry"
175+
176+
hass.config_entries.async_entries(DOMAIN)[0].data == {"apps": {"test_app": {"param": 1}}}

tests/test_decorator_errors.py

+3-13
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22
from ast import literal_eval
33
import asyncio
44
from datetime import datetime as dt
5-
import pathlib
65

76
from custom_components.pyscript.const import DOMAIN
87
import custom_components.pyscript.trigger as trigger
98
from pytest_homeassistant.async_mock import mock_open, patch
109

11-
from homeassistant import loader
1210
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EVENT_STATE_CHANGED
1311
from homeassistant.setup import async_setup_component
1412

@@ -18,18 +16,10 @@ async def setup_script(hass, notify_q, now, source):
1816
scripts = [
1917
"/some/config/dir/pyscripts/hello.py",
2018
]
21-
integration = loader.Integration(
22-
hass,
23-
"custom_components.pyscript",
24-
pathlib.Path("custom_components/pyscript"),
25-
{"name": "pyscript", "dependencies": [], "requirements": [], "domain": "automation"},
26-
)
2719

28-
with patch("homeassistant.loader.async_get_integration", return_value=integration), patch(
29-
"custom_components.pyscript.os.path.isdir", return_value=True
30-
), patch("custom_components.pyscript.glob.iglob", return_value=scripts), patch(
31-
"custom_components.pyscript.global_ctx.open", mock_open(read_data=source), create=True,
32-
), patch(
20+
with patch("custom_components.pyscript.os.path.isdir", return_value=True), patch(
21+
"custom_components.pyscript.glob.iglob", return_value=scripts
22+
), patch("custom_components.pyscript.global_ctx.open", mock_open(read_data=source), create=True,), patch(
3323
"custom_components.pyscript.trigger.dt_now", return_value=now
3424
):
3525
assert await async_setup_component(hass, "pyscript", {DOMAIN: {}})

tests/test_function.py

+3-13
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from ast import literal_eval
33
import asyncio
44
from datetime import datetime as dt
5-
import pathlib
65
import time
76

87
from custom_components.pyscript.const import DOMAIN
@@ -11,7 +10,6 @@
1110
import pytest
1211
from pytest_homeassistant.async_mock import MagicMock, Mock, mock_open, patch
1312

14-
from homeassistant import loader
1513
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EVENT_STATE_CHANGED
1614
from homeassistant.setup import async_setup_component
1715

@@ -104,18 +102,10 @@ async def setup_script(hass, notify_q, now, source):
104102
scripts = [
105103
"/some/config/dir/pyscripts/hello.py",
106104
]
107-
integration = loader.Integration(
108-
hass,
109-
"custom_components.pyscript",
110-
pathlib.Path("custom_components/pyscript"),
111-
{"name": "pyscript", "dependencies": [], "requirements": [], "domain": "automation"},
112-
)
113105

114-
with patch("homeassistant.loader.async_get_integration", return_value=integration), patch(
115-
"custom_components.pyscript.os.path.isdir", return_value=True
116-
), patch("custom_components.pyscript.glob.iglob", return_value=scripts), patch(
117-
"custom_components.pyscript.global_ctx.open", mock_open(read_data=source), create=True,
118-
), patch(
106+
with patch("custom_components.pyscript.os.path.isdir", return_value=True), patch(
107+
"custom_components.pyscript.glob.iglob", return_value=scripts
108+
), patch("custom_components.pyscript.global_ctx.open", mock_open(read_data=source), create=True,), patch(
119109
"custom_components.pyscript.trigger.dt_now", return_value=now
120110
):
121111
assert await async_setup_component(hass, "pyscript", {DOMAIN: {}})

tests/test_init.py

+11-32
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,10 @@ async def setup_script(hass, notify_q, now, source):
2424
scripts = [
2525
"/some/config/dir/pyscript/hello.py",
2626
]
27-
integration = loader.Integration(
28-
hass,
29-
"custom_components.pyscript",
30-
pathlib.Path("custom_components/pyscript"),
31-
{"name": "pyscript", "dependencies": [], "requirements": [], "domain": "automation"},
32-
)
3327

34-
with patch("homeassistant.loader.async_get_integration", return_value=integration), patch(
35-
"custom_components.pyscript.os.path.isdir", return_value=True
36-
), patch("custom_components.pyscript.glob.iglob", return_value=scripts), patch(
37-
"custom_components.pyscript.global_ctx.open", mock_open(read_data=source), create=True,
38-
), patch(
28+
with patch("custom_components.pyscript.os.path.isdir", return_value=True), patch(
29+
"custom_components.pyscript.glob.iglob", return_value=scripts
30+
), patch("custom_components.pyscript.global_ctx.open", mock_open(read_data=source), create=True,), patch(
3931
"custom_components.pyscript.trigger.dt_now", return_value=now
4032
), patch(
4133
"homeassistant.config.load_yaml_config_file", return_value={}
@@ -67,16 +59,9 @@ async def wait_until_done(notify_q):
6759

6860
async def test_setup_makedirs_on_no_dir(hass, caplog):
6961
"""Test setup calls os.makedirs when no dir found."""
70-
integration = loader.Integration(
71-
hass,
72-
"custom_components.pyscript",
73-
pathlib.Path("custom_components/pyscript"),
74-
{"name": "pyscript", "dependencies": [], "requirements": [], "domain": "automation"},
75-
)
76-
77-
with patch("homeassistant.loader.async_get_integration", return_value=integration), patch(
78-
"custom_components.pyscript.os.path.isdir", return_value=False
79-
), patch("custom_components.pyscript.os.makedirs") as makedirs_call:
62+
with patch("custom_components.pyscript.os.path.isdir", return_value=False), patch(
63+
"custom_components.pyscript.os.makedirs"
64+
) as makedirs_call:
8065
res = await async_setup_component(hass, "pyscript", {DOMAIN: {}})
8166

8267
assert res
@@ -237,7 +222,7 @@ def func_yaml_doc_string(param2=None, param3=None):
237222
{"name": "pyscript", "dependencies": [], "requirements": [], "domain": "automation"},
238223
)
239224

240-
with patch("homeassistant.loader.async_get_integration", return_value=integration), patch(
225+
with patch(
241226
"homeassistant.loader.async_get_custom_components", return_value={"pyscript": integration},
242227
):
243228
descriptions = await async_get_all_descriptions(hass)
@@ -442,16 +427,10 @@ def func5(var_name=None, value=None):
442427
scripts = [
443428
"/some/config/dir/pyscript/hello.py",
444429
]
445-
integration = loader.Integration(
446-
hass,
447-
"custom_components.pyscript",
448-
pathlib.Path("custom_components/pyscript"),
449-
{"name": "pyscript", "dependencies": [], "requirements": [], "domain": "automation"},
450-
)
451-
452-
with patch("homeassistant.loader.async_get_integration", return_value=integration), patch(
453-
"custom_components.pyscript.os.path.isdir", return_value=True
454-
), patch("custom_components.pyscript.glob.iglob", return_value=scripts), patch(
430+
431+
with patch("custom_components.pyscript.os.path.isdir", return_value=True), patch(
432+
"custom_components.pyscript.glob.iglob", return_value=scripts
433+
), patch(
455434
"custom_components.pyscript.global_ctx.open", mock_open(read_data=next_source), create=True,
456435
), patch(
457436
"custom_components.pyscript.trigger.dt_now", return_value=now

0 commit comments

Comments
 (0)