From c57d2bdf008c5704ea68725ab1ac8ac9bfc8a912 Mon Sep 17 00:00:00 2001 From: wasd Date: Sun, 3 May 2026 22:55:45 +0200 Subject: [PATCH] Fix escaping XPath characters --- README.md | 2 +- sagemcom_api/client.py | 64 ++++++++++++++++++------------------------ 2 files changed, 28 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 75ad55d..4e0bc9a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ (Unofficial) async Python client to interact with Sagemcom F@st routers via internal API's. This client offers helper functions to retrieve common used functions, but also offers functionality to do custom requests via XPATH notation. -Python 3.9+ required. +Python 3.11+ required. ## Features diff --git a/sagemcom_api/client.py b/sagemcom_api/client.py index 1951cb9..3a16e64 100644 --- a/sagemcom_api/client.py +++ b/sagemcom_api/client.py @@ -381,34 +381,14 @@ async def get_encryption_method(self): return None - @backoff.on_exception( - backoff.expo, - ( - AuthenticationException, - LoginRetryErrorException, - LoginTimeoutException, - InvalidSessionException, - ), - max_tries=1, - on_backoff=retry_login, - ) async def get_value_by_xpath(self, xpath: str, options: dict | None = None) -> dict: """Retrieve raw value from router using XPath. :param xpath: path expression :param options: optional options """ - actions = { - "id": 0, - "method": "getValue", - "xpath": urllib.parse.quote(xpath, "/=[]'"), - "options": options if options else {}, - } - - response = await self.__api_request_async([actions], False) - data = self.__get_response_value(response) - - return data + result = await self.get_values_by_xpaths({"value": xpath}, options) + return result["value"] @backoff.on_exception( backoff.expo, @@ -421,7 +401,7 @@ async def get_value_by_xpath(self, xpath: str, options: dict | None = None) -> d max_tries=1, on_backoff=retry_login, ) - async def get_values_by_xpaths(self, xpaths, options: dict | None = None) -> dict: + async def get_values_by_xpaths(self, xpaths: dict[str, str], options: dict | None = None) -> dict: """Retrieve raw values from router using XPath. :param xpaths: Dict of key to xpath expression @@ -431,7 +411,7 @@ async def get_values_by_xpaths(self, xpaths, options: dict | None = None) -> dic { "id": i, "method": "getValue", - "xpath": urllib.parse.quote(xpath, "/=[]'"), + "xpath": urllib.parse.quote(xpath, "/=[]'@\""), "options": options if options else {}, } for i, xpath in enumerate(xpaths.values()) @@ -443,6 +423,15 @@ async def get_values_by_xpaths(self, xpaths, options: dict | None = None) -> dic return data + async def set_value_by_xpath(self, xpath: str, value: str, options: dict | None = None) -> dict: + """Set value using XPath. + + :param xpath: path expression + :param value: value + :param options: optional options + """ + return await self.set_values_by_xpaths({xpath: value}, options) + @backoff.on_exception( backoff.expo, ( @@ -454,23 +443,24 @@ async def get_values_by_xpaths(self, xpaths, options: dict | None = None) -> dic max_tries=1, on_backoff=retry_login, ) - async def set_value_by_xpath(self, xpath: str, value: str, options: dict | None = None) -> dict: - """Retrieve raw value from router using XPath. + async def set_values_by_xpaths(self, xpaths: dict[str, str], options: dict | None = None) -> dict: + """Set multiple values using XPath. - :param xpath: path expression - :param value: value + :param xpaths: Dict of xpath expression to value :param options: optional options """ - actions = { - "id": 0, - "method": "setValue", - "xpath": urllib.parse.quote(xpath, "/=[]'"), - "parameters": {"value": str(value)}, - "options": options if options else {}, - } - - response = await self.__api_request_async([actions], False) + actions = [ + { + "id": i, + "method": "setValue", + "xpath": urllib.parse.quote(xpath, "/=[]'@\""), + "parameters": {"value": str(value)}, + "options": options if options else {}, + } + for i, (xpath, value) in enumerate(xpaths.items()) + ] + response = await self.__api_request_async(actions, False) return response @backoff.on_exception(