Skip to content

Commit d44f9b0

Browse files
authored
Fix escaping XPath characters (#476)
This fixes XPath expressions like `get_value_by_xpath("Device/Hosts/Hosts/Host[@uid=\"8258\"]/UserFriendlyName")`. Also added a method `set_values_by_xpaths` to set multiple values in one call, as was available to get multiple values. Updated README.md to reflect actual python version needed according to pyproject.toml.
1 parent a7625ed commit d44f9b0

2 files changed

Lines changed: 28 additions & 38 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
(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.
44

5-
Python 3.9+ required.
5+
Python 3.11+ required.
66

77
## Features
88

sagemcom_api/client.py

Lines changed: 27 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -381,34 +381,14 @@ async def get_encryption_method(self):
381381

382382
return None
383383

384-
@backoff.on_exception(
385-
backoff.expo,
386-
(
387-
AuthenticationException,
388-
LoginRetryErrorException,
389-
LoginTimeoutException,
390-
InvalidSessionException,
391-
),
392-
max_tries=1,
393-
on_backoff=retry_login,
394-
)
395384
async def get_value_by_xpath(self, xpath: str, options: dict | None = None) -> dict:
396385
"""Retrieve raw value from router using XPath.
397386
398387
:param xpath: path expression
399388
:param options: optional options
400389
"""
401-
actions = {
402-
"id": 0,
403-
"method": "getValue",
404-
"xpath": urllib.parse.quote(xpath, "/=[]'"),
405-
"options": options if options else {},
406-
}
407-
408-
response = await self.__api_request_async([actions], False)
409-
data = self.__get_response_value(response)
410-
411-
return data
390+
result = await self.get_values_by_xpaths({"value": xpath}, options)
391+
return result["value"]
412392

413393
@backoff.on_exception(
414394
backoff.expo,
@@ -421,7 +401,7 @@ async def get_value_by_xpath(self, xpath: str, options: dict | None = None) -> d
421401
max_tries=1,
422402
on_backoff=retry_login,
423403
)
424-
async def get_values_by_xpaths(self, xpaths, options: dict | None = None) -> dict:
404+
async def get_values_by_xpaths(self, xpaths: dict[str, str], options: dict | None = None) -> dict:
425405
"""Retrieve raw values from router using XPath.
426406
427407
: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
431411
{
432412
"id": i,
433413
"method": "getValue",
434-
"xpath": urllib.parse.quote(xpath, "/=[]'"),
414+
"xpath": urllib.parse.quote(xpath, "/=[]'@\""),
435415
"options": options if options else {},
436416
}
437417
for i, xpath in enumerate(xpaths.values())
@@ -443,6 +423,15 @@ async def get_values_by_xpaths(self, xpaths, options: dict | None = None) -> dic
443423

444424
return data
445425

426+
async def set_value_by_xpath(self, xpath: str, value: str, options: dict | None = None) -> dict:
427+
"""Set value using XPath.
428+
429+
:param xpath: path expression
430+
:param value: value
431+
:param options: optional options
432+
"""
433+
return await self.set_values_by_xpaths({xpath: value}, options)
434+
446435
@backoff.on_exception(
447436
backoff.expo,
448437
(
@@ -454,23 +443,24 @@ async def get_values_by_xpaths(self, xpaths, options: dict | None = None) -> dic
454443
max_tries=1,
455444
on_backoff=retry_login,
456445
)
457-
async def set_value_by_xpath(self, xpath: str, value: str, options: dict | None = None) -> dict:
458-
"""Retrieve raw value from router using XPath.
446+
async def set_values_by_xpaths(self, xpaths: dict[str, str], options: dict | None = None) -> dict:
447+
"""Set multiple values using XPath.
459448
460-
:param xpath: path expression
461-
:param value: value
449+
:param xpaths: Dict of xpath expression to value
462450
:param options: optional options
463451
"""
464-
actions = {
465-
"id": 0,
466-
"method": "setValue",
467-
"xpath": urllib.parse.quote(xpath, "/=[]'"),
468-
"parameters": {"value": str(value)},
469-
"options": options if options else {},
470-
}
471-
472-
response = await self.__api_request_async([actions], False)
452+
actions = [
453+
{
454+
"id": i,
455+
"method": "setValue",
456+
"xpath": urllib.parse.quote(xpath, "/=[]'@\""),
457+
"parameters": {"value": str(value)},
458+
"options": options if options else {},
459+
}
460+
for i, (xpath, value) in enumerate(xpaths.items())
461+
]
473462

463+
response = await self.__api_request_async(actions, False)
474464
return response
475465

476466
@backoff.on_exception(

0 commit comments

Comments
 (0)