diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index edd66e3..3aa3f49 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.12" + python-version: "3.13" - 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.12" + python-version: "3.13" - 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.12" + python-version: "3.13" - 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.12" + python-version: "3.13" - run: python3 -m pip install -r tests/requirements_test.txt - run: mypy custom_components/pyscript/*.py tests/*.py diff --git a/custom_components/pyscript/__init__.py b/custom_components/pyscript/__init__.py index c0f0137..7195666 100644 --- a/custom_components/pyscript/__init__.py +++ b/custom_components/pyscript/__init__.py @@ -22,10 +22,10 @@ SERVICE_RELOAD, ) from homeassistant.core import Event as HAEvent, HomeAssistant, ServiceCall -from homeassistant.helpers.typing import ConfigType from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.restore_state import DATA_RESTORE_STATE +from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass from .const import ( @@ -151,7 +151,7 @@ class WatchDogHandler(FileSystemEventHandler): """Class for handling watchdog events.""" def __init__( - self, watchdog_q: asyncio.Queue, observer: watchdog.observers.Observer, path: str + self, watchdog_q: asyncio.Queue, observer: watchdog.observers.ObserverType, path: str ) -> None: self.watchdog_q = watchdog_q self._observer = observer diff --git a/custom_components/pyscript/manifest.json b/custom_components/pyscript/manifest.json index 7ff4d13..deae17f 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==6.0.0", "watchdog==6.0.0"], "ssdp": [], "version": "1.6.1", "zeroconf": [] diff --git a/docs/new_features.rst b/docs/new_features.rst index 44d831e..3fd393d 100644 --- a/docs/new_features.rst +++ b/docs/new_features.rst @@ -20,3 +20,24 @@ to see the development version of the documentation. If you want to see development progress since 1.6.1, see `new features `__ in the latest documentation, or look at the `GitHub repository `__. + +Planned new features post 1.6.1 include: + +- Services defined in pyscript should support entity methods if they include an ``entity_id`` keyword argument. +- Consider supporting the built-in functions that do I/O, such as ``open``, ``read`` and ``write``, which + are not currently supported to avoid I/O in the main event loop, and also to avoid security issues if people + share pyscripts. The ``print`` function only logs a message, rather than implements the real ``print`` features, + such as specifying an output file handle. Support might be added in the future using an executor job, perhaps + enabled when ``allow_all_imports`` is set. + +The new features since 1.6.1 in master include: + +None yet. + +Breaking changes since 1.6.1 include: + +None yet. + +Bug fixes since 1.6.1 include: + +None yet. diff --git a/pylintrc b/pylintrc index a3a97cf..3b78f42 100644 --- a/pylintrc +++ b/pylintrc @@ -42,6 +42,7 @@ disable= too-many-instance-attributes, too-many-lines, too-many-locals, + too-many-positional-arguments, too-many-public-methods, too-many-return-statements, too-many-statements, diff --git a/pyproject.toml b/pyproject.toml index 39de95a..8b9737b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,6 @@ [tool.black] line-length = 109 + +#[tool.pytest.ini_options] +#asyncio_mode = "auto" +#asyncio_default_fixture_loop_scope = "function" diff --git a/tests/requirements_test.txt b/tests/requirements_test.txt index 7dd1a4e..5b7e859 100644 --- a/tests/requirements_test.txt +++ b/tests/requirements_test.txt @@ -1,11 +1,11 @@ -coverage==7.5.3 -croniter==2.0.2 -watchdog==2.3.1 +coverage==7.6.8 +croniter==6.0.0 +watchdog==6.0.0 mock-open==1.4.0 mypy==1.10.1 pre-commit==3.7.1 -pytest==8.2.0 -pytest-cov==5.0.0 -pytest-homeassistant-custom-component==0.13.145 -pylint==3.2.5 -pylint-strict-informational==0.1 \ No newline at end of file +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-homeassistant-custom-component==0.13.201 +pylint==3.3.2 +pylint-strict-informational==0.1 diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index 9f57747..413a50e 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -26,12 +26,12 @@ async def test_user_flow_minimum_fields(hass, pyscript_bypass_setup): """Test user config flow with minimum fields.""" # test form shows result = await hass.config_entries.flow.async_init(DOMAIN, context={"source": SOURCE_USER}) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "user" result = await hass.config_entries.flow.async_configure(result["flow_id"], user_input={}) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY assert CONF_ALLOW_ALL_IMPORTS in result["data"] assert CONF_HASS_IS_GLOBAL in result["data"] assert not result["data"][CONF_ALLOW_ALL_IMPORTS] @@ -44,14 +44,14 @@ async def test_user_flow_all_fields(hass, pyscript_bypass_setup): # test form shows result = await hass.config_entries.flow.async_init(DOMAIN, context={"source": SOURCE_USER}) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "user" result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_ALLOW_ALL_IMPORTS: True, CONF_HASS_IS_GLOBAL: True} ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY assert CONF_ALLOW_ALL_IMPORTS in result["data"] assert result["data"][CONF_ALLOW_ALL_IMPORTS] assert result["data"][CONF_HASS_IS_GLOBAL] @@ -66,7 +66,7 @@ async def test_user_already_configured(hass, pyscript_bypass_setup): data={CONF_ALLOW_ALL_IMPORTS: True, CONF_HASS_IS_GLOBAL: True}, ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY result = await hass.config_entries.flow.async_init( DOMAIN, @@ -74,7 +74,7 @@ async def test_user_already_configured(hass, pyscript_bypass_setup): data={CONF_ALLOW_ALL_IMPORTS: True, CONF_HASS_IS_GLOBAL: True}, ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["type"] == data_entry_flow.FlowResultType.ABORT assert result["reason"] == "single_instance_allowed" @@ -85,7 +85,7 @@ async def test_import_flow(hass, pyscript_bypass_setup): DOMAIN, context={"source": SOURCE_IMPORT}, data=PYSCRIPT_SCHEMA({}) ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY @pytest.mark.asyncio @@ -95,7 +95,7 @@ async def test_import_flow_update_allow_all_imports(hass, pyscript_bypass_setup) DOMAIN, context={"source": SOURCE_IMPORT}, data=PYSCRIPT_SCHEMA({}) ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY result = await hass.config_entries.flow.async_init( DOMAIN, @@ -103,7 +103,7 @@ async def test_import_flow_update_allow_all_imports(hass, pyscript_bypass_setup) data={CONF_ALLOW_ALL_IMPORTS: True, CONF_HASS_IS_GLOBAL: True}, ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["type"] == data_entry_flow.FlowResultType.ABORT assert result["reason"] == "updated_entry" @@ -114,13 +114,13 @@ async def test_import_flow_update_apps_from_none(hass, pyscript_bypass_setup): DOMAIN, context={"source": SOURCE_IMPORT}, data=PYSCRIPT_SCHEMA({}) ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data={"apps": {"test_app": {"param": 1}}} ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["type"] == data_entry_flow.FlowResultType.ABORT assert result["reason"] == "updated_entry" @@ -131,11 +131,11 @@ async def test_import_flow_update_apps_to_none(hass, pyscript_bypass_setup): DOMAIN, context={"source": SOURCE_IMPORT}, data=PYSCRIPT_SCHEMA({"apps": {"test_app": {"param": 1}}}) ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY result = await hass.config_entries.flow.async_init(DOMAIN, context={"source": SOURCE_IMPORT}, data={}) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["type"] == data_entry_flow.FlowResultType.ABORT assert result["reason"] == "updated_entry" @@ -146,13 +146,13 @@ async def test_import_flow_no_update(hass, pyscript_bypass_setup): DOMAIN, context={"source": SOURCE_IMPORT}, data=PYSCRIPT_SCHEMA({}) ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=PYSCRIPT_SCHEMA({}) ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["type"] == data_entry_flow.FlowResultType.ABORT assert result["reason"] == "already_configured" @@ -165,13 +165,13 @@ async def test_import_flow_update_user(hass, pyscript_bypass_setup): data=PYSCRIPT_SCHEMA({CONF_ALLOW_ALL_IMPORTS: True, CONF_HASS_IS_GLOBAL: True}), ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data={"apps": {"test_app": {"param": 1}}} ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["type"] == data_entry_flow.FlowResultType.ABORT assert result["reason"] == "updated_entry" assert hass.config_entries.async_entries(DOMAIN)[0].data == { @@ -190,13 +190,13 @@ async def test_import_flow_update_import(hass, pyscript_bypass_setup): data=PYSCRIPT_SCHEMA({CONF_ALLOW_ALL_IMPORTS: True, CONF_HASS_IS_GLOBAL: True}), ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data={"apps": {"test_app": {"param": 1}}} ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["type"] == data_entry_flow.FlowResultType.ABORT assert result["reason"] == "updated_entry" assert hass.config_entries.async_entries(DOMAIN)[0].data == {"apps": {"test_app": {"param": 1}}} @@ -211,17 +211,17 @@ async def test_options_flow_import(hass, pyscript_bypass_setup): data=PYSCRIPT_SCHEMA({CONF_ALLOW_ALL_IMPORTS: True, CONF_HASS_IS_GLOBAL: True}), ) await hass.async_block_till_done() - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY entry = result["result"] result = await hass.config_entries.options.async_init(entry.entry_id, data=None) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "no_ui_configuration_allowed" result = await hass.config_entries.options.async_configure(result["flow_id"], user_input=None) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY assert result["title"] == "" @@ -234,12 +234,12 @@ async def test_options_flow_user_change(hass, pyscript_bypass_setup): data=PYSCRIPT_SCHEMA({CONF_ALLOW_ALL_IMPORTS: True, CONF_HASS_IS_GLOBAL: True}), ) await hass.async_block_till_done() - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY entry = result["result"] result = await hass.config_entries.options.async_init(entry.entry_id) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "init" result = await hass.config_entries.options.async_configure( @@ -247,7 +247,7 @@ async def test_options_flow_user_change(hass, pyscript_bypass_setup): ) await hass.async_block_till_done() - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY assert result["title"] == "" assert entry.data[CONF_ALLOW_ALL_IMPORTS] is False @@ -263,24 +263,24 @@ async def test_options_flow_user_no_change(hass, pyscript_bypass_setup): data=PYSCRIPT_SCHEMA({CONF_ALLOW_ALL_IMPORTS: True, CONF_HASS_IS_GLOBAL: True}), ) await hass.async_block_till_done() - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY entry = result["result"] result = await hass.config_entries.options.async_init(entry.entry_id) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "init" result = await hass.config_entries.options.async_configure( result["flow_id"], user_input={CONF_ALLOW_ALL_IMPORTS: True, CONF_HASS_IS_GLOBAL: True} ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "no_update" result = await hass.config_entries.options.async_configure(result["flow_id"], user_input=None) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY assert result["title"] == "" @@ -296,7 +296,7 @@ async def test_config_entry_reload(hass): data=PYSCRIPT_SCHEMA({CONF_ALLOW_ALL_IMPORTS: True, CONF_HASS_IS_GLOBAL: True}), ) await hass.async_block_till_done() - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY entry = result["result"] listeners = hass.bus.async_listeners() await hass.config_entries.async_reload(entry.entry_id)