Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
- uses: "actions/checkout@v2"
- uses: "actions/setup-python@v1"
with:
python-version: "3.13"
python-version: "3.14"
allow-prereleases: true
- run: python3 -m pip install -r tests/requirements_test.txt
- run: pytest --cov=custom_components
Expand All @@ -42,7 +42,7 @@ jobs:
- uses: "actions/checkout@v2"
- uses: "actions/setup-python@v1"
with:
python-version: "3.13"
python-version: "3.14"
allow-prereleases: true
- run: python3 -m pip install -r tests/requirements_test.txt
- run: pylint custom_components/pyscript/*.py tests/*.py
Expand All @@ -54,7 +54,7 @@ jobs:
- uses: "actions/checkout@v2"
- uses: "actions/setup-python@v1"
with:
python-version: "3.13"
python-version: "3.14"
allow-prereleases: true
- run: python3 -m pip install -r tests/requirements_test.txt
- run: mypy custom_components/pyscript/*.py tests/*.py
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.14
rev: v0.15.12
hooks:
- id: ruff-check
args: [--fix, --show-fixes]
Expand Down
6 changes: 4 additions & 2 deletions custom_components/pyscript/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import json
import logging
import os
from os import makedirs as os_makedirs
from os.path import isdir as os_path_isdir
import shutil
import time
import traceback
Expand Down Expand Up @@ -279,9 +281,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
DecoratorRegistry.init(hass, config_entry)

pyscript_folder = hass.config.path(FOLDER)
if not await hass.async_add_executor_job(os.path.isdir, pyscript_folder):
if not await hass.async_add_executor_job(os_path_isdir, pyscript_folder):
_LOGGER.debug("Folder %s not found in configuration folder, creating it", FOLDER)
await hass.async_add_executor_job(os.makedirs, pyscript_folder)
await hass.async_add_executor_job(os_makedirs, pyscript_folder)

hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][CONFIG_ENTRY] = config_entry
Expand Down
2 changes: 1 addition & 1 deletion custom_components/pyscript/decorator_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def __init__(self, ast_ctx: AstEval, name: str) -> None:
"""Initialize the manager."""
self.ast_ctx = ast_ctx
self.name = name
self.func_name = name.split(".")[-1]
self.func_name = name.rsplit(".", maxsplit=1)[-1]
self.logger = ast_ctx.get_logger()

self.status: DecoratorManagerStatus = DecoratorManagerStatus.INIT
Expand Down
4 changes: 2 additions & 2 deletions custom_components/pyscript/eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -1132,7 +1132,7 @@ async def ast_functiondef(self, arg, async_func=False):

func = self.sym_table[arg.name]
if dec_name == "pyscript_executor":
if not asyncio.iscoroutinefunction(func):
if not inspect.iscoroutinefunction(func):

def executor_wrap_factory(func):
async def executor_wrap(*args, **kwargs):
Expand Down Expand Up @@ -1903,7 +1903,7 @@ async def call_func(self, func, func_name, *args, **kwargs):
#
await inst.__init__evalfunc_wrap__.call(self, *args, **kwargs)
return inst
if asyncio.iscoroutinefunction(func):
if inspect.iscoroutinefunction(func):
return await func(*args, **kwargs)
if callable(func):
if func == time.sleep: # pylint: disable=comparison-with-callable
Expand Down
3 changes: 2 additions & 1 deletion custom_components/pyscript/global_ctx.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from collections.abc import Awaitable, Callable
import logging
import os
from os.path import isfile as os_path_isfile
from types import ModuleType
from typing import Any, ClassVar

Expand Down Expand Up @@ -167,7 +168,7 @@ async def module_import(self, module_name: str, import_level: int) -> ModuleType
def find_first_file(file_paths: list[list[str]]) -> list[str] | None:
for ctx_name, path, rel_path in file_paths:
abs_path = os.path.join(pyscript_dir, path)
if os.path.isfile(abs_path):
if os_path_isfile(abs_path):
return [ctx_name, abs_path, rel_path]
return None

Expand Down
10 changes: 5 additions & 5 deletions custom_components/pyscript/jupyter_kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ async def control_listen(self, reader, writer):
await self.housekeep_q.put(["shutdown"])
except asyncio.CancelledError:
raise
except (EOFError, ConnectionResetError):
except EOFError, ConnectionResetError:
_LOGGER.debug("control_listen got eof")
await self.housekeep_q.put(["unregister", "control", asyncio.current_task()])
control_socket.close()
Expand All @@ -630,7 +630,7 @@ async def stdin_listen(self, reader, writer):
# _LOGGER.debug("stdin_listen received %s", _)
except asyncio.CancelledError:
raise
except (EOFError, ConnectionResetError):
except EOFError, ConnectionResetError:
_LOGGER.debug("stdin_listen got eof")
await self.housekeep_q.put(["unregister", "stdin", asyncio.current_task()])
stdin_socket.close()
Expand All @@ -651,7 +651,7 @@ async def shell_listen(self, reader, writer):
except asyncio.CancelledError:
shell_socket.close()
raise
except (EOFError, ConnectionResetError):
except EOFError, ConnectionResetError:
_LOGGER.debug("shell_listen got eof")
await self.housekeep_q.put(["unregister", "shell", asyncio.current_task()])
shell_socket.close()
Expand All @@ -672,7 +672,7 @@ async def heartbeat_listen(self, reader, writer):
await heartbeat_socket.send(msg)
except asyncio.CancelledError:
raise
except (EOFError, ConnectionResetError):
except EOFError, ConnectionResetError:
_LOGGER.debug("heartbeat_listen got eof")
await self.housekeep_q.put(["unregister", "heartbeat", asyncio.current_task()])
heartbeat_socket.close()
Expand All @@ -693,7 +693,7 @@ async def iopub_listen(self, reader, writer):
# _LOGGER.debug("iopub received %s", _)
except asyncio.CancelledError:
raise
except (EOFError, ConnectionResetError):
except EOFError, ConnectionResetError:
await self.housekeep_q.put(["unregister", "iopub", asyncio.current_task()])
iopub_socket.close()
self.iopub_socket.discard(iopub_socket)
Expand Down
24 changes: 13 additions & 11 deletions custom_components/pyscript/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
from homeassistant.core import Context, HomeAssistant, State as CoreState
from homeassistant.helpers.restore_state import DATA_RESTORE_STATE
from homeassistant.helpers.service import async_get_all_descriptions
from homeassistant.helpers.template import (
from homeassistant.helpers.template.extensions.math import _SENTINEL as MATH_SENTINEL, MathExtension
from homeassistant.helpers.template.extensions.type_cast import (
_SENTINEL as TYPECAST_SENTINEL,
TypeCastExtension,
)
from homeassistant.helpers.template.helpers import (
_SENTINEL,
forgiving_boolean,
forgiving_float,
forgiving_int,
forgiving_round,
raise_no_default,
)
from homeassistant.util import dt as dt_util
Expand All @@ -41,27 +43,27 @@ def __new__(cls, state: CoreState) -> Self:
new_var.last_reported = state.last_reported
return new_var

def as_float(self, default: float = _SENTINEL) -> float:
def as_float(self, default: float = TYPECAST_SENTINEL) -> float:
"""Return the state converted to float via the forgiving helper."""
return forgiving_float(self, default=default)
return TypeCastExtension.forgiving_float(self, default=default)

def as_int(self, default: int = _SENTINEL, base: int = 10) -> int:
def as_int(self, default: int = TYPECAST_SENTINEL, base: int = 10) -> int:
"""Return the state converted to int via the forgiving helper."""
return forgiving_int(self, default=default, base=base)
return TypeCastExtension.forgiving_int(self, default=default, base=base)

def as_bool(self, default: bool = _SENTINEL) -> bool:
"""Return the state converted to bool via the forgiving helper."""
return forgiving_boolean(self, default=default)

def as_round(self, precision: int = 0, method: str = "common", default: float = _SENTINEL) -> float:
def as_round(self, precision: int = 0, method: str = "common", default: float = MATH_SENTINEL) -> float:
"""Return the rounded state value via the forgiving helper."""
return forgiving_round(self, precision=precision, method=method, default=default)
return MathExtension.forgiving_round(self, precision=precision, method=method, default=default)

def as_datetime(self, default: datetime = _SENTINEL) -> datetime:
"""Return the state converted to a datetime, matching the forgiving template behaviour."""
try:
return dt_util.parse_datetime(self, raise_on_error=True)
except (ValueError, TypeError):
except ValueError, TypeError:
if default is _SENTINEL:
raise_no_default("as_datetime", self)
return default
Expand Down
3 changes: 2 additions & 1 deletion custom_components/pyscript/trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import asyncio
import datetime as dt
import functools
import inspect
import locale
import logging
import math
Expand Down Expand Up @@ -598,7 +599,7 @@ async def wait_until(
@classmethod
async def user_task_executor(cls, func, *args, **kwargs):
"""Implement task.executor()."""
if asyncio.iscoroutinefunction(func) or not callable(func):
if inspect.iscoroutinefunction(func) or not callable(func):
raise TypeError(f"function {func} is not callable by task.executor")
if isinstance(func, EvalFuncVar):
raise TypeError(
Expand Down
3 changes: 2 additions & 1 deletion hacs.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"content_in_root": false,
"domains": ["automation", "script", "timer"],
"zip_release": true,
"filename": "hass-custom-pyscript.zip"
"filename": "hass-custom-pyscript.zip",
"homeassistant": "2026.5.0"
}
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,5 @@ ignore = [

[dependency-groups]
dev = [
"ruff<0.15.0",
]
"ruff==0.15.12",
]
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ addopts =
--asyncio-mode=auto

[mypy]
python_version = 3.13
python_version = 3.14
ignore_errors = true
follow_imports = silent
ignore_missing_imports = true
Expand Down
10 changes: 5 additions & 5 deletions tests/requirements_test.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
coverage==7.10.6
coverage==7.13.5
croniter==6.0.0
watchdog==6.0.0
mock-open==1.4.0
mypy==1.10.1
pycares<5.0.0
pre-commit==3.7.1
pytest==9.0.0
pytest-cov==7.0.0
pytest-homeassistant-custom-component==0.13.299
pytest==9.0.3
pytest-cov==7.1.0
pytest-homeassistant-custom-component==0.13.326
ruff==0.15.12
pylint==3.3.4
pylint-strict-informational==0.1
2 changes: 1 addition & 1 deletion tests/test_apps_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def glob_side_effect(path, recursive=None, root_dir=None, dir_fd=None, include_h
patch("custom_components.pyscript.watchdog_start", return_value=None),
patch("custom_components.pyscript.os.path.getmtime", return_value=1000),
patch("custom_components.pyscript.global_ctx.os.path.getmtime", return_value=1000),
patch("custom_components.pyscript.os.path.isfile") as mock_isfile,
patch("custom_components.pyscript.global_ctx.os_path_isfile") as mock_isfile,
):
mock_isfile.side_effect = isfile_side_effect
mock_glob.side_effect = glob_side_effect
Expand Down
2 changes: 1 addition & 1 deletion tests/test_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def glob_side_effect(path, recursive=None, root_dir=None, dir_fd=None, include_h
patch("custom_components.pyscript.watchdog_start", return_value=None),
patch("custom_components.pyscript.os.path.getmtime", return_value=1000),
patch("custom_components.pyscript.global_ctx.os.path.getmtime", return_value=1000),
patch("custom_components.pyscript.os.path.isfile") as mock_isfile,
patch("custom_components.pyscript.global_ctx.os_path_isfile") as mock_isfile,
):
mock_isfile.side_effect = isfile_side_effect
mock_glob.side_effect = glob_side_effect
Expand Down
8 changes: 4 additions & 4 deletions tests/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ async def setup_script(hass, notify_q, now, source, script_name="/hello.py"):
Function.hass = None

with (
patch("custom_components.pyscript.os.path.isdir", return_value=True),
patch("custom_components.pyscript.os_path_isdir", return_value=True),
patch("custom_components.pyscript.glob.iglob", return_value=scripts),
patch("custom_components.pyscript.global_ctx.open", mock_open(read_data=source)),
patch("custom_components.pyscript.trigger.dt_now", return_value=now),
Expand Down Expand Up @@ -74,8 +74,8 @@ async def wait_until_done(notify_q):
async def test_setup_makedirs_on_no_dir(hass, caplog):
"""Test setup calls os.makedirs when no dir found."""
with (
patch("custom_components.pyscript.os.path.isdir", return_value=False),
patch("custom_components.pyscript.os.makedirs"),
patch("custom_components.pyscript.os_path_isdir", return_value=False),
patch("custom_components.pyscript.os_makedirs"),
patch("custom_components.pyscript.watchdog_start", return_value=None) as makedirs_call,
patch("homeassistant.config.load_yaml_config_file", return_value={}),
):
Expand Down Expand Up @@ -463,7 +463,7 @@ def func5(var_name=None, value=None):
]

with (
patch("custom_components.pyscript.os.path.isdir", return_value=True),
patch("custom_components.pyscript.os_path_isdir", return_value=True),
patch("custom_components.pyscript.glob.iglob", return_value=scripts),
patch("custom_components.pyscript.global_ctx.open", mock_open(read_data=next_source)),
patch("custom_components.pyscript.open", mock_open(read_data=next_source)),
Expand Down
2 changes: 1 addition & 1 deletion tests/test_jupyter.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def glob_side_effect(path, recursive=None, root_dir=None, dir_fd=None, include_h
patch("custom_components.pyscript.watchdog_start", return_value=None),
patch("custom_components.pyscript.os.path.getmtime", return_value=1000),
patch("custom_components.pyscript.global_ctx.os.path.getmtime", return_value=1000),
patch("custom_components.pyscript.os.path.isfile") as mock_isfile,
patch("custom_components.pyscript.global_ctx.os_path_isfile") as mock_isfile,
):
mock_isfile.side_effect = isfile_side_effect
mock_glob.side_effect = glob_side_effect
Expand Down
2 changes: 1 addition & 1 deletion tests/test_reload.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def glob_side_effect(path, recursive=None, root_dir=None, dir_fd=None, include_h
patch("custom_components.pyscript.watchdog_start", return_value=None),
patch("custom_components.pyscript.os.path.getmtime", return_value=1000),
patch("custom_components.pyscript.global_ctx.os.path.getmtime", return_value=1000),
patch("custom_components.pyscript.os.path.isfile") as mock_isfile,
patch("custom_components.pyscript.global_ctx.os_path_isfile") as mock_isfile,
):
mock_isfile.side_effect = isfile_side_effect
mock_glob.side_effect = glob_side_effect
Expand Down
2 changes: 1 addition & 1 deletion tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def glob_side_effect(path, recursive=None, root_dir=None, dir_fd=None, include_h
patch("custom_components.pyscript.os.path.getmtime", return_value=1000),
patch("custom_components.pyscript.watchdog_start", return_value=None),
patch("custom_components.pyscript.global_ctx.os.path.getmtime", return_value=1000),
patch("custom_components.pyscript.os.path.isfile") as mock_isfile,
patch("custom_components.pyscript.global_ctx.os_path_isfile") as mock_isfile,
):
mock_isfile.side_effect = isfile_side_effect
mock_glob.side_effect = glob_side_effect
Expand Down
Loading