From 069e0c0f42d7101d5534623be97ec5e95d62ddb7 Mon Sep 17 00:00:00 2001 From: John Lancaster <32917998+jsl12@users.noreply.github.com> Date: Thu, 2 May 2024 22:27:46 -0500 Subject: [PATCH] packaged with rye for export_mode --- .python-version | 1 + pyproject.toml | 32 +++++ requirements-dev.lock | 115 ++++++++++++++++++ requirements.lock | 115 ++++++++++++++++++ __init__.py => src/room_control/__init__.py | 0 button.py => src/room_control/button.py | 6 +- console.py => src/room_control/console.py | 6 +- door.py => src/room_control/door.py | 8 +- model.py => src/room_control/model.py | 16 ++- motion.py => src/room_control/motion.py | 28 +++-- .../room_control/room_control.py | 5 +- 11 files changed, 316 insertions(+), 16 deletions(-) create mode 100644 .python-version create mode 100644 pyproject.toml create mode 100644 requirements-dev.lock create mode 100644 requirements.lock rename __init__.py => src/room_control/__init__.py (100%) rename button.py => src/room_control/button.py (94%) rename console.py => src/room_control/console.py (97%) rename door.py => src/room_control/door.py (71%) rename model.py => src/room_control/model.py (91%) rename motion.py => src/room_control/motion.py (85%) rename room_control.py => src/room_control/room_control.py (98%) diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..2419ad5 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.11.9 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3f3a8de --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +[project] +name = "room-control" +version = "0.1.0" +description = "Add your description here" +authors = [ + { name = "John Lancaster", email = "32917998+jsl12@users.noreply.github.com" } +] +dependencies = [ + "appdaemon>=4.4.2", + "rich>=13.7.1", + "pydantic>=2.7.1", + "ruff>=0.4.2", +] +readme = "README.md" +requires-python = ">= 3.8,<3.12" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.ruff.format] +quote-style = 'single' + +[tool.rye] +managed = true +dev-dependencies = [] + +[tool.hatch.metadata] +allow-direct-references = true + +[tool.hatch.build.targets.wheel] +packages = ["src/room_control"] diff --git a/requirements-dev.lock b/requirements-dev.lock new file mode 100644 index 0000000..03874e0 --- /dev/null +++ b/requirements-dev.lock @@ -0,0 +1,115 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false + +-e file:. +aiohttp==3.8.6 + # via aiohttp-jinja2 + # via appdaemon + # via sockjs +aiohttp-jinja2==1.5.1 + # via appdaemon +aiosignal==1.3.1 + # via aiohttp +annotated-types==0.6.0 + # via pydantic +appdaemon==4.4.2 + # via room-control +astral==3.2 + # via appdaemon +async-timeout==4.0.3 + # via aiohttp +attrs==23.2.0 + # via aiohttp +bcrypt==4.0.1 + # via appdaemon +bidict==0.23.1 + # via python-socketio +certifi==2024.2.2 + # via requests +charset-normalizer==3.3.2 + # via aiohttp + # via requests +deepdiff==6.3.0 + # via appdaemon +feedparser==6.0.11 + # via appdaemon +frozenlist==1.4.1 + # via aiohttp + # via aiosignal +h11==0.14.0 + # via wsproto +idna==3.7 + # via requests + # via yarl +iso8601==1.1.0 + # via appdaemon +jinja2==3.1.3 + # via aiohttp-jinja2 +markdown-it-py==3.0.0 + # via rich +markupsafe==2.1.5 + # via jinja2 +mdurl==0.1.2 + # via markdown-it-py +multidict==6.0.5 + # via aiohttp + # via yarl +ordered-set==4.1.0 + # via deepdiff +paho-mqtt==1.6.1 + # via appdaemon +pid==3.0.4 + # via appdaemon +pydantic==2.7.1 + # via room-control +pydantic-core==2.18.2 + # via pydantic +pygments==2.17.2 + # via rich +python-dateutil==2.8.2 + # via appdaemon +python-engineio==4.9.0 + # via python-socketio +python-socketio==5.8.0 + # via appdaemon +pytz==2023.3.post1 + # via appdaemon +pyyaml==6.0.1 + # via appdaemon +requests==2.28.2 + # via appdaemon +rich==13.7.1 + # via room-control +ruff==0.4.2 + # via room-control +sgmllib3k==1.0.0 + # via feedparser +simple-websocket==1.0.0 + # via python-engineio +six==1.16.0 + # via python-dateutil +sockjs==0.11.0 + # via appdaemon +tomli==2.0.1 + # via appdaemon +tomli-w==1.0.0 + # via appdaemon +typing-extensions==4.11.0 + # via pydantic + # via pydantic-core +urllib3==1.26.18 + # via requests +uvloop==0.17.0 + # via appdaemon +websocket-client==1.5.3 + # via appdaemon +wsproto==1.2.0 + # via simple-websocket +yarl==1.9.4 + # via aiohttp diff --git a/requirements.lock b/requirements.lock new file mode 100644 index 0000000..03874e0 --- /dev/null +++ b/requirements.lock @@ -0,0 +1,115 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false + +-e file:. +aiohttp==3.8.6 + # via aiohttp-jinja2 + # via appdaemon + # via sockjs +aiohttp-jinja2==1.5.1 + # via appdaemon +aiosignal==1.3.1 + # via aiohttp +annotated-types==0.6.0 + # via pydantic +appdaemon==4.4.2 + # via room-control +astral==3.2 + # via appdaemon +async-timeout==4.0.3 + # via aiohttp +attrs==23.2.0 + # via aiohttp +bcrypt==4.0.1 + # via appdaemon +bidict==0.23.1 + # via python-socketio +certifi==2024.2.2 + # via requests +charset-normalizer==3.3.2 + # via aiohttp + # via requests +deepdiff==6.3.0 + # via appdaemon +feedparser==6.0.11 + # via appdaemon +frozenlist==1.4.1 + # via aiohttp + # via aiosignal +h11==0.14.0 + # via wsproto +idna==3.7 + # via requests + # via yarl +iso8601==1.1.0 + # via appdaemon +jinja2==3.1.3 + # via aiohttp-jinja2 +markdown-it-py==3.0.0 + # via rich +markupsafe==2.1.5 + # via jinja2 +mdurl==0.1.2 + # via markdown-it-py +multidict==6.0.5 + # via aiohttp + # via yarl +ordered-set==4.1.0 + # via deepdiff +paho-mqtt==1.6.1 + # via appdaemon +pid==3.0.4 + # via appdaemon +pydantic==2.7.1 + # via room-control +pydantic-core==2.18.2 + # via pydantic +pygments==2.17.2 + # via rich +python-dateutil==2.8.2 + # via appdaemon +python-engineio==4.9.0 + # via python-socketio +python-socketio==5.8.0 + # via appdaemon +pytz==2023.3.post1 + # via appdaemon +pyyaml==6.0.1 + # via appdaemon +requests==2.28.2 + # via appdaemon +rich==13.7.1 + # via room-control +ruff==0.4.2 + # via room-control +sgmllib3k==1.0.0 + # via feedparser +simple-websocket==1.0.0 + # via python-engineio +six==1.16.0 + # via python-dateutil +sockjs==0.11.0 + # via appdaemon +tomli==2.0.1 + # via appdaemon +tomli-w==1.0.0 + # via appdaemon +typing-extensions==4.11.0 + # via pydantic + # via pydantic-core +urllib3==1.26.18 + # via requests +uvloop==0.17.0 + # via appdaemon +websocket-client==1.5.3 + # via appdaemon +wsproto==1.2.0 + # via simple-websocket +yarl==1.9.4 + # via aiohttp diff --git a/__init__.py b/src/room_control/__init__.py similarity index 100% rename from __init__.py rename to src/room_control/__init__.py diff --git a/button.py b/src/room_control/button.py similarity index 94% rename from button.py rename to src/room_control/button.py index ae73fbd..7fab7aa 100644 --- a/button.py +++ b/src/room_control/button.py @@ -38,7 +38,11 @@ class Button(Mqtt): topic = f'zigbee2mqtt/{name}' # self.mqtt_subscribe(topic, namespace='mqtt') self.listen_event( - self.handle_button, 'MQTT_MESSAGE', topic=topic, namespace='mqtt', button=name + self.handle_button, + 'MQTT_MESSAGE', + topic=topic, + namespace='mqtt', + button=name, ) self.log(f'MQTT topic [topic]{topic}[/] controls app [room]{self.app.name}[/]') diff --git a/console.py b/src/room_control/console.py similarity index 97% rename from console.py rename to src/room_control/console.py index feea06c..23d9363 100644 --- a/console.py +++ b/src/room_control/console.py @@ -108,7 +108,11 @@ def room_logging_config(name: str): }, }, 'handlers': { - 'rich_room': {'formatter': 'rich_room', 'filters': ['room'], **RICH_HANDLER_CFG}, + 'rich_room': { + 'formatter': 'rich_room', + 'filters': ['room'], + **RICH_HANDLER_CFG, + }, 'file': { 'filters': ['unmarkup'], 'formatter': 'file', diff --git a/door.py b/src/room_control/door.py similarity index 71% rename from door.py rename to src/room_control/door.py index 2a24e2f..184d85c 100644 --- a/door.py +++ b/src/room_control/door.py @@ -13,5 +13,9 @@ class Door(Hass): self.app: RoomController = await self.get_app(self.args['app']) self.log(f'Connected to AD app [room]{self.app.name}[/]', level='DEBUG') - await self.listen_state(self.app.activate_all_off, entity_id=self.args['door'], new='on', cause='door open') - + await self.listen_state( + self.app.activate_all_off, + entity_id=self.args['door'], + new='on', + cause='door open', + ) diff --git a/model.py b/src/room_control/model.py similarity index 91% rename from model.py rename to src/room_control/model.py index ef3b1fd..def1003 100644 --- a/model.py +++ b/src/room_control/model.py @@ -47,7 +47,9 @@ class ApplyKwargs(BaseModel): class ControllerStateConfig(BaseModel): time: Optional[str | datetime] = None elevation: Optional[float] = None - direction: Optional[Annotated[SunDirection, BeforeValidator(str_to_direction)]] = None + direction: Optional[Annotated[SunDirection, BeforeValidator(str_to_direction)]] = ( + None + ) off_duration: Optional[OffDuration] = None scene: dict[str, State] | str @@ -55,9 +57,13 @@ class ControllerStateConfig(BaseModel): def check_args(cls, values): time, elevation = values.get('time'), values.get('elevation') if time is not None and elevation is not None: - raise PydanticCustomError('bad_time_spec', 'Only one of time or elevation can be set.') + raise PydanticCustomError( + 'bad_time_spec', 'Only one of time or elevation can be set.' + ) elif elevation is not None and 'direction' not in values: - raise PydanticCustomError('no_sun_dir', 'Needs sun direction with elevation') + raise PydanticCustomError( + 'no_sun_dir', 'Needs sun direction with elevation' + ) return values def to_apply_kwargs(self, **kwargs): @@ -79,7 +85,9 @@ class RoomControllerConfig(BaseModel): if app_cfg['class'] == 'RoomController': return cls.model_validate(app_cfg) - def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult: + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: table = Table( Column('Time', width=15), Column('Scene'), diff --git a/motion.py b/src/room_control/motion.py similarity index 85% rename from motion.py rename to src/room_control/motion.py index 1ca200a..4b5aa05 100644 --- a/motion.py +++ b/src/room_control/motion.py @@ -65,11 +65,14 @@ class Motion(Hass): if self.state_mismatch: self.log( - f'Sensor is {self.sensor_state} ' f'and light is {self.ref_entity_state}', + f'Sensor is {self.sensor_state} ' + f'and light is {self.ref_entity_state}', level='WARNING', ) if self.sensor_state: - self.app.activate(kwargs={'cause': f'Syncing state with {self.sensor.entity_id}'}) + self.app.activate( + kwargs={'cause': f'Syncing state with {self.sensor.entity_id}'} + ) # don't need to await these because they'll already get turned into a task by the utils.sync_wrapper decorator self.listen_state( @@ -81,7 +84,9 @@ class Motion(Hass): if callbacks := self.callbacks(): for handle, entry in callbacks.items(): - self.log(f'Handle [yellow]{handle[:4]}[/]: {entry.function}', level='DEBUG') + self.log( + f'Handle [yellow]{handle[:4]}[/]: {entry.function}', level='DEBUG' + ) def callbacks(self): """Returns a dictionary of validated CallbackEntry objects that are associated with this app""" @@ -98,7 +103,9 @@ class Motion(Hass): oneshot=True, cause='motion on', ) - self.log(f'Waiting for sensor motion on [friendly_name]{self.sensor.friendly_name}[/]') + self.log( + f'Waiting for sensor motion on [friendly_name]{self.sensor.friendly_name}[/]' + ) if self.sensor_state: self.log( f'Sensor [friendly_name]{self.sensor.friendly_name}[/] is already on', @@ -126,14 +133,18 @@ class Motion(Hass): level='WARNING', ) - def callback_light_on(self, entity=None, attribute=None, old=None, new=None, kwargs=None): + def callback_light_on( + self, entity=None, attribute=None, old=None, new=None, kwargs=None + ): """Called when the light turns on""" if new is not None: self.log(f'Detected {entity} turning on', level='DEBUG') duration = self.app.off_duration() self.listen_motion_off(duration) - def callback_light_off(self, entity=None, attribute=None, old=None, new=None, kwargs=None): + def callback_light_off( + self, entity=None, attribute=None, old=None, new=None, kwargs=None + ): """Called when the light turns off""" self.log(f'Detected {entity} turning off', level='DEBUG') self.listen_motion_on() @@ -165,4 +176,7 @@ class Motion(Hass): if (m := re.match('new=(?P.*?)\s', kwargs)) is not None: new = m.group('new') self.cancel_listen_state(handle) - self.log(f'cancelled callback for sensor {entity} turning {new}', level='DEBUG') + self.log( + f'cancelled callback for sensor {entity} turning {new}', + level='DEBUG', + ) diff --git a/room_control.py b/src/room_control/room_control.py similarity index 98% rename from room_control.py rename to src/room_control/room_control.py index 6468c5f..c1b2464 100755 --- a/room_control.py +++ b/src/room_control/room_control.py @@ -79,7 +79,10 @@ class RoomController(Hass, Mqtt): """ # re-parse the state strings into times for the current day self._room_config = RoomControllerConfig.model_validate(self.args) - self.log(f'{len(self._room_config.states)} states in the app configuration', level='DEBUG') + self.log( + f'{len(self._room_config.states)} states in the app configuration', + level='DEBUG', + ) for state in self._room_config.states: if state.time is None and state.elevation is not None: