diff --git a/appdaemon/models/config/plugin.py b/appdaemon/models/config/plugin.py index 2da1ca1c5..93aa66cf1 100644 --- a/appdaemon/models/config/plugin.py +++ b/appdaemon/models/config/plugin.py @@ -3,9 +3,9 @@ from ssl import _SSLMethod from typing import Annotated, Any, Literal -from pydantic import AnyHttpUrl, BaseModel, BeforeValidator, Field, SecretBytes, SecretStr, field_validator, model_validator +from pydantic import BaseModel, BeforeValidator, ConfigDict, Field, PlainSerializer, SecretBytes, SecretStr, field_validator, model_validator from typing_extensions import deprecated - +from yarl import URL from .common import CoercedPath, ParsedTimedelta @@ -86,7 +86,11 @@ class StartupConditions(BaseModel): class HASSConfig(PluginConfig, extra="forbid"): - ha_url: AnyHttpUrl = Field(default="http://supervisor/core", validate_default=True) # pyright: ignore[reportAssignmentType] + ha_url: Annotated[ + URL, + BeforeValidator(URL), + PlainSerializer(str), + ] = Field(default="http://supervisor/core", validate_default=True) # pyright: ignore[reportAssignmentType] token: SecretStr = Field(default_factory=lambda: SecretStr(os.environ.get("SUPERVISOR_TOKEN"))) # pyright: ignore[reportArgumentType] ha_key: Annotated[SecretStr, deprecated("'ha_key' is deprecated. Please use long lived tokens instead")] | None = None appdaemon_startup_conditions: StartupConditions | None = None @@ -108,6 +112,8 @@ class HASSConfig(PluginConfig, extra="forbid"): config_sleep_time: ParsedTimedelta = timedelta(seconds=60) """The sleep time in the background task that updates the config metadata every once in a while""" + model_config = ConfigDict(arbitrary_types_allowed=True) + @model_validator(mode="after") def custom_validator(self): if self.token.get_secret_value() is None: @@ -117,15 +123,8 @@ def custom_validator(self): return self @property - def websocket_url(self) -> str: - return f"{self.ha_url!s}api/websocket" - - @property - def states_api(self) -> str: - return f"{self.ha_url!s}api/states" - - def get_entity_api(self, entity_id: str) -> str: - return f"{self.states_api}/{entity_id}" + def websocket_url(self) -> URL: + return self.ha_url / "api/websocket" @property def auth_json(self) -> dict: diff --git a/appdaemon/plugins/hass/hassplugin.py b/appdaemon/plugins/hass/hassplugin.py index b47f7ddeb..1b67cc97f 100644 --- a/appdaemon/plugins/hass/hassplugin.py +++ b/appdaemon/plugins/hass/hassplugin.py @@ -14,7 +14,7 @@ from typing import Any, Literal, Optional import aiohttp -from aiohttp import ClientResponse, ClientResponseError, RequestInfo, WSMsgType, WebSocketError +from aiohttp import ClientResponse, ClientResponseError, RequestInfo, WebSocketError, WSMsgType from pydantic import BaseModel import appdaemon.utils as utils @@ -456,11 +456,11 @@ async def http_method( appropriate. """ kwargs = utils.clean_http_kwargs(kwargs) - url = utils.make_endpoint(f"{self.config.ha_url!s}", endpoint) + url = self.config.ha_url / endpoint.lstrip("/") try: self.update_perf( - bytes_sent=len(url) + len(json.dumps(kwargs).encode("utf-8")), + bytes_sent=len(str(url)) + len(json.dumps(kwargs).encode("utf-8")), requests_sent=1, ) @@ -884,8 +884,7 @@ async def set_plugin_state( @utils.warning_decorator(error_text=f"Error setting state for {entity_id}") async def safe_set_state(self: "HassPlugin"): - api_url = self.config.get_entity_api(entity_id) - return await self.http_method("post", api_url, state=state, attributes=attributes) + return await self.http_method("post", f"/api/states/{entity_id}", state=state, attributes=attributes) return await safe_set_state(self) diff --git a/appdaemon/utils.py b/appdaemon/utils.py index 9e3552295..e6095b5a5 100644 --- a/appdaemon/utils.py +++ b/appdaemon/utils.py @@ -1167,15 +1167,6 @@ def clean_http_kwargs(val: Any) -> Any: return pruned -def make_endpoint(base: str, endpoint: str) -> str: - """Formats a URL appropriately with slashes""" - if not endpoint.startswith(base): - result = f"{base}/{endpoint.strip('/')}" - else: - result = endpoint - return result.strip("/") - - def unwrapped(func: Callable) -> Callable: while hasattr(func, "__wrapped__"): func = func.__wrapped__