diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..2781521 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,49 @@ +name: Publish to PyPI + +on: + push: + tags: + - "v*" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # needed for setuptools_scm to derive version from tags + + - uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install build dependencies + run: pip install build + + - name: Build distributions + run: python -m build + + - name: Upload distributions as artifact + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + + publish: + needs: build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/carconnectivity-plugin-campermode + permissions: + id-token: write # required for OIDC trusted publisher + + steps: + - name: Download distributions + uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/pyproject.toml b/pyproject.toml index 70e9c29..d6f4f47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,7 @@ description = "CarConnectivity plugin for overnight climatisation cycling (campe dynamic = ["version"] requires-python = ">=3.10" authors = [{ name = "Andreas Schneider" }] +keywords = ["carconnectivity", "volkswagen", "vw", "climatisation", "camper"] dependencies = [ "carconnectivity>=0.11.5", "Werkzeug~=3.1.3", @@ -22,8 +23,22 @@ license-files = ["LICENSE"] classifiers = [ "Development Status :: 5 - Production/Stable", "Topic :: Home Automation", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] +[project.urls] +Homepage = "https://github.com/cryptomilk/CarConnectivity-plugin-campermode" +Repository = "https://github.com/cryptomilk/CarConnectivity-plugin-campermode" +Issues = "https://github.com/cryptomilk/CarConnectivity-plugin-campermode/issues" + +[project.entry-points."carconnectivity.plugins"] +campermode = "carconnectivity_plugins.campermode.plugin:Plugin" + [tool.setuptools.package-data] "carconnectivity_plugins.campermode" = [ "ui/templates/campermode/*.html", diff --git a/src/carconnectivity_plugins/campermode/models.py b/src/carconnectivity_plugins/campermode/models.py index 51ee427..2de4fc8 100644 --- a/src/carconnectivity_plugins/campermode/models.py +++ b/src/carconnectivity_plugins/campermode/models.py @@ -8,7 +8,7 @@ from dataclasses import dataclass, field, replace from datetime import datetime, time from enum import Enum -from typing import cast +from typing import Any, cast LOG: logging.Logger = logging.getLogger("carconnectivity.plugins.campermode") @@ -133,18 +133,12 @@ def to_dict(self) -> dict[str, object]: } @classmethod - def from_dict(cls, data: dict[str, object]) -> CamperSettings: + def from_dict(cls, data: dict[str, Any]) -> CamperSettings: return cls( - min_battery_level=int(data.get("min_battery_level", 20)), # type: ignore[arg-type] - minutes_between_cycles=int( - data.get("minutes_between_cycles", 0) # type: ignore[arg-type] - ), - cycle_duration_minutes=int( - data.get("cycle_duration_minutes", 30) # type: ignore[arg-type] - ), - total_duration_minutes=int( - data.get("total_duration_minutes", 60) # type: ignore[arg-type] - ), + min_battery_level=int(data.get("min_battery_level", 20)), + minutes_between_cycles=int(data.get("minutes_between_cycles", 0)), + cycle_duration_minutes=int(data.get("cycle_duration_minutes", 30)), + total_duration_minutes=int(data.get("total_duration_minutes", 60)), endless=bool(data.get("endless", False)), extra_heating_enabled=bool( data.get("extra_heating_enabled", False) @@ -155,9 +149,7 @@ def from_dict(cls, data: dict[str, object]) -> CamperSettings: front_zone_right=bool(data.get("front_zone_right", False)), rear_zone_left=bool(data.get("rear_zone_left", False)), rear_zone_right=bool(data.get("rear_zone_right", False)), - target_temperature=float( - data.get("target_temperature", 22.0) # type: ignore[arg-type] - ), + target_temperature=float(data.get("target_temperature", 22.0)), ) diff --git a/src/carconnectivity_plugins/campermode/scheduler.py b/src/carconnectivity_plugins/campermode/scheduler.py index ba4174c..0b3e3b6 100644 --- a/src/carconnectivity_plugins/campermode/scheduler.py +++ b/src/carconnectivity_plugins/campermode/scheduler.py @@ -384,7 +384,7 @@ def _apply_climate_settings(self) -> None: # VW-specific per-zone seat/window heating try: - from carconnectivity_connectors.volkswagen.climatization import ( # type: ignore[import-not-found] + from carconnectivity_connectors.volkswagen.climatization import ( # ty: ignore[unresolved-import] VolkswagenClimatization, ) diff --git a/src/carconnectivity_plugins/campermode/ui/plugin_ui.py b/src/carconnectivity_plugins/campermode/ui/plugin_ui.py index d73b3cc..a2bee4c 100644 --- a/src/carconnectivity_plugins/campermode/ui/plugin_ui.py +++ b/src/carconnectivity_plugins/campermode/ui/plugin_ui.py @@ -44,6 +44,11 @@ class LoginForm(FlaskForm): submit = SubmitField("Login") +class _User(flask_login.UserMixin): + def __init__(self, username: str) -> None: + self.id: str = username + + class CamperUI: """Standalone Flask web server for the CamperMode plugin.""" @@ -94,8 +99,7 @@ def _user_loader( ) -> flask_login.UserMixin | None: if username not in self.users: return None - user = flask_login.UserMixin() - user.id = username # type: ignore[assignment] + user = _User(username) return user @login_manager.request_loader @@ -114,8 +118,7 @@ def _load_from_request( username, password = decoded.split(":", 1) stored = self.users.get(username, {}).get("password", "") if hmac.compare_digest(stored, password): - user = flask_login.UserMixin() - user.id = username # type: ignore[assignment] + user = _User(username) return user return None @@ -165,8 +168,7 @@ def login() -> WerkzeugResponse | str: if stored_pwd and hmac.compare_digest( stored_pwd, form.password.data or "" ): - user = flask_login.UserMixin() - user.id = username # type: ignore[assignment] + user = _User(username) flask_login.login_user( user, remember=bool(form.remember_me.data) )