From 4d8b5572b3defda408c73b0662b2dbcebd2dd09f Mon Sep 17 00:00:00 2001 From: IgnusG <6438760+IgnusG@users.noreply.github.com> Date: Sun, 16 Jun 2024 18:56:22 +0200 Subject: [PATCH 1/2] Upgrade dependencies and fix issues --- .github/workflows/validate.yml | 8 ++++---- custom_components/pyscript/manifest.json | 2 +- tests/requirements_test.txt | 18 +++++++++--------- tests/test_function.py | 8 ++++---- tests/test_state.py | 6 +++--- tests/test_unit_eval.py | 1 + 6 files changed, 22 insertions(+), 21 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index a739ccd..edd66e3 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -18,7 +18,7 @@ jobs: - uses: "actions/checkout@v2" - uses: "actions/setup-python@v1" with: - python-version: "3.11" + python-version: "3.12" - run: python3 -m pip install black - run: black . @@ -29,7 +29,7 @@ jobs: - uses: "actions/checkout@v2" - uses: "actions/setup-python@v1" with: - python-version: "3.11" + python-version: "3.12" - run: python3 -m pip install -r tests/requirements_test.txt - run: pytest --cov=custom_components @@ -40,7 +40,7 @@ jobs: - uses: "actions/checkout@v2" - uses: "actions/setup-python@v1" with: - python-version: "3.11" + python-version: "3.12" - run: python3 -m pip install -r tests/requirements_test.txt - run: pylint custom_components/pyscript/*.py tests/*.py @@ -51,6 +51,6 @@ jobs: - uses: "actions/checkout@v2" - uses: "actions/setup-python@v1" with: - python-version: "3.11" + python-version: "3.12" - run: python3 -m pip install -r tests/requirements_test.txt - run: mypy custom_components/pyscript/*.py tests/*.py diff --git a/custom_components/pyscript/manifest.json b/custom_components/pyscript/manifest.json index 4125574..99cd5b4 100644 --- a/custom_components/pyscript/manifest.json +++ b/custom_components/pyscript/manifest.json @@ -10,7 +10,7 @@ "homekit": {}, "iot_class": "local_push", "issue_tracker": "https://github.com/custom-components/pyscript/issues", - "requirements": ["croniter==2.0.2", "watchdog==2.3.1"], + "requirements": ["croniter==2.0.5", "watchdog==4.0.1"], "ssdp": [], "version": "1.5.0", "zeroconf": [] diff --git a/tests/requirements_test.txt b/tests/requirements_test.txt index e2a813a..8593a87 100644 --- a/tests/requirements_test.txt +++ b/tests/requirements_test.txt @@ -1,11 +1,11 @@ -coverage==7.2.4 -croniter==1.3.8 -watchdog==2.1.9 +coverage==7.5.0 +croniter==2.0.5 +watchdog==4.0.1 mock-open==1.4.0 -mypy==1.3.0 -pre-commit==3.2.1 -pytest==7.3.1 -pytest-cov==3.0.0 -pytest-homeassistant-custom-component==0.13.45 -pylint==2.17.4 +mypy==1.10.0 +pre-commit==3.7.1 +pytest==8.2.0 +pytest-cov==5.0.0 +pytest-homeassistant-custom-component==0.13.135 +pylint==3.2.3 pylint-strict-informational==0.1 diff --git a/tests/test_function.py b/tests/test_function.py index 2391c19..332e1c3 100644 --- a/tests/test_function.py +++ b/tests/test_function.py @@ -13,7 +13,7 @@ from custom_components.pyscript.const import CONF_ALLOW_ALL_IMPORTS, CONF_HASS_IS_GLOBAL, DOMAIN, FOLDER from custom_components.pyscript.function import Function from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EVENT_STATE_CHANGED -from homeassistant.core import Context +from homeassistant.core import Context, ServiceRegistry from homeassistant.setup import async_setup_component @@ -95,7 +95,7 @@ async def test_func_completions( @pytest.mark.asyncio async def test_service_completions(root, expected, hass, services): # pylint: disable=redefined-outer-name """Test service name completion.""" - with patch.object(hass.services, "async_services", return_value=services), patch.object( + with patch.object(ServiceRegistry, "async_services", return_value=services), patch.object( Function, "hass", hass ): words = await Function.service_completions(root) @@ -1247,10 +1247,10 @@ def service_call_exception(): @pytest.mark.asyncio async def test_service_call_params(hass): """Test that hass params get set properly on service calls.""" - with patch.object(hass.services, "async_call") as call, patch.object( + with patch.object(ServiceRegistry, "async_call") as call, patch.object( Function, "service_has_service", return_value=True ), patch.object( - hass.services, + ServiceRegistry, "supports_response", return_value="none", ): diff --git a/tests/test_state.py b/tests/test_state.py index 8207f40..5a07070 100644 --- a/tests/test_state.py +++ b/tests/test_state.py @@ -6,7 +6,7 @@ from custom_components.pyscript.function import Function from custom_components.pyscript.state import State -from homeassistant.core import Context +from homeassistant.core import Context, ServiceRegistry, StateMachine from homeassistant.helpers.state import State as HassState @@ -20,8 +20,8 @@ async def test_service_call(hass): "test": {"description": None, "fields": {"entity_id": "blah", "other_service_data": "blah"}} } }, - ), patch.object(hass.states, "get", return_value=HassState("test.entity", "True")), patch.object( - hass.services, "async_call" + ), patch.object(StateMachine, "get", return_value=HassState("test.entity", "True")), patch.object( + ServiceRegistry, "async_call" ) as call: State.init(hass) Function.init(hass) diff --git a/tests/test_unit_eval.py b/tests/test_unit_eval.py index 3bb5699..c3f55a5 100644 --- a/tests/test_unit_eval.py +++ b/tests/test_unit_eval.py @@ -1458,6 +1458,7 @@ async def test_eval(hass): "syntax error invalid syntax (, line 1)", # < 3.9 "syntax error f-string: invalid syntax (test, line 1)", # >= 3.9 "syntax error f-string: invalid syntax. Perhaps you forgot a comma? (test, line 1)", # >= 3.10 + "syntax error invalid syntax. Perhaps you forgot a comma? (test, line 1)", # >= 3.12 }, ], ["del xx", "Exception in test line 1 column 0: name 'xx' is not defined"], From 884c428131746b845a8844b0239cc9facf379e27 Mon Sep 17 00:00:00 2001 From: IgnusG <6438760+IgnusG@users.noreply.github.com> Date: Sun, 16 Jun 2024 19:39:10 +0200 Subject: [PATCH 2/2] Make location sunset/sunrise async --- custom_components/pyscript/trigger.py | 36 +++++++++++++++------------ 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/custom_components/pyscript/trigger.py b/custom_components/pyscript/trigger.py index ece082e..875a7c7 100644 --- a/custom_components/pyscript/trigger.py +++ b/custom_components/pyscript/trigger.py @@ -400,7 +400,7 @@ async def wait_until( if startup_time is None: startup_time = now if time_trigger is not None: - time_next, time_next_adj = cls.timer_trigger_next(time_trigger, now, startup_time) + time_next, time_next_adj = await cls.timer_trigger_next(time_trigger, now, startup_time) _LOGGER.debug( "trigger %s wait_until time_next = %s, now = %s", ast_ctx.name, @@ -605,7 +605,7 @@ async def user_task_executor(cls, func, *args, **kwargs): return await cls.hass.async_add_executor_job(functools.partial(func, **kwargs), *args) @classmethod - def parse_date_time(cls, date_time_str, day_offset, now, startup_time): + async def parse_date_time(cls, date_time_str, day_offset, now, startup_time): """Parse a date time string, returning datetime.""" year = now.year month = now.month @@ -670,10 +670,14 @@ def parse_date_time(cls, date_time_str, day_offset, now, startup_time): location = location[0] try: if dt_str.startswith("sunrise"): - time_sun = location.sunrise(dt.date(year, month, day)) + time_sun = await cls.hass.async_add_executor_job( + location.sunrise, dt.date(year, month, day) + ) dt_str = dt_str[7:] else: - time_sun = location.sunset(dt.date(year, month, day)) + time_sun = await cls.hass.async_add_executor_job( + location.sunset, dt.date(year, month, day) + ) dt_str = dt_str[6:] except Exception: _LOGGER.warning("'%s' not defined at this latitude", dt_str) @@ -706,7 +710,7 @@ def parse_date_time(cls, date_time_str, day_offset, now, startup_time): return now @classmethod - def timer_active_check(cls, time_spec, now, startup_time): + async def timer_active_check(cls, time_spec, now, startup_time): """Check if the given time matches the time specification.""" results = {"+": [], "-": []} for entry in time_spec if isinstance(time_spec, list) else [time_spec]: @@ -733,8 +737,8 @@ def timer_active_check(cls, time_spec, now, startup_time): _LOGGER.error("Invalid range expression: %s", exc) return False - start = cls.parse_date_time(dt_start.strip(), 0, now, startup_time) - end = cls.parse_date_time(dt_end.strip(), 0, start, startup_time) + start = await cls.parse_date_time(dt_start.strip(), 0, now, startup_time) + end = await cls.parse_date_time(dt_end.strip(), 0, start, startup_time) if start <= end: this_match = start <= now <= end @@ -755,7 +759,7 @@ def timer_active_check(cls, time_spec, now, startup_time): return result @classmethod - def timer_trigger_next(cls, time_spec, now, startup_time): + async def timer_trigger_next(cls, time_spec, now, startup_time): """Return the next trigger time based on the given time and time specification.""" next_time = None next_time_adj = None @@ -798,20 +802,20 @@ def timer_trigger_next(cls, time_spec, now, startup_time): next_time_adj = now + delta elif len(match1) == 3: - this_t = cls.parse_date_time(match1[1].strip(), 0, now, startup_time) + this_t = await cls.parse_date_time(match1[1].strip(), 0, now, startup_time) day_offset = (now - this_t).days + 1 if day_offset != 0 and this_t != startup_time: # # Try a day offset (won't make a difference if spec has full date) # - this_t = cls.parse_date_time(match1[1].strip(), day_offset, now, startup_time) + this_t = await cls.parse_date_time(match1[1].strip(), day_offset, now, startup_time) startup = now == this_t and now == startup_time if (now < this_t or startup) and (next_time is None or this_t < next_time): next_time_adj = next_time = this_t elif len(match2) == 5: start_str, period_str = match2[1].strip(), match2[2].strip() - start = cls.parse_date_time(start_str, 0, now, startup_time) + start = await cls.parse_date_time(start_str, 0, now, startup_time) period = parse_time_offset(period_str) if period <= 0: _LOGGER.error("Invalid non-positive period %s in period(): %s", period, time_spec) @@ -828,11 +832,11 @@ def timer_trigger_next(cls, time_spec, now, startup_time): next_time_adj = next_time = this_t continue end_str = match2[3].strip() - end = cls.parse_date_time(end_str, 0, now, startup_time) + end = await cls.parse_date_time(end_str, 0, now, startup_time) end_offset = 1 if end < start else 0 for day in [-1, 0, 1]: - start = cls.parse_date_time(start_str, day, now, startup_time) - end = cls.parse_date_time(end_str, day + end_offset, now, startup_time) + start = await cls.parse_date_time(start_str, day, now, startup_time) + end = await cls.parse_date_time(end_str, day + end_offset, now, startup_time) if now < start or (now == start and now == startup_time): if next_time is None or start < next_time: next_time_adj = next_time = start @@ -1108,7 +1112,7 @@ async def trigger_watch(self): check_state_expr_on_start = False else: if self.time_trigger: - time_next, time_next_adj = TrigTime.timer_trigger_next( + time_next, time_next_adj = await TrigTime.timer_trigger_next( self.time_trigger, now, startup_time ) _LOGGER.debug( @@ -1279,7 +1283,7 @@ async def trigger_watch(self): self.active_expr.get_logger().error(exc) trig_ok = False if trig_ok and self.time_active: - trig_ok = TrigTime.timer_active_check(self.time_active, now, startup_time) + trig_ok = await TrigTime.timer_active_check(self.time_active, now, startup_time) if not trig_ok: _LOGGER.debug(