Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-fritzconnection for openSUSE:Factory checked in at 2025-07-14 10:52:07 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-fritzconnection (Old) and /work/SRC/openSUSE:Factory/.python-fritzconnection.new.7373 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-fritzconnection" Mon Jul 14 10:52:07 2025 rev:12 rq:1292503 version:1.15.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-fritzconnection/python-fritzconnection.changes 2025-06-17 18:23:43.978075940 +0200 +++ /work/SRC/openSUSE:Factory/.python-fritzconnection.new.7373/python-fritzconnection.changes 2025-07-14 10:57:53.512026949 +0200 @@ -1,0 +2,25 @@ +Sun Jul 13 13:09:15 UTC 2025 - Dirk Müller <dmuel...@suse.com> + +- update to 1.15.0: + * support added for Python 3.14 + * FritzConnection: + - additional argument `redact_debug_log` (default: False) for + optional redacting response in debug output. (#238) (#241) + - new method `get_cpu_temperatures()` providing a list of the + last recent cpu-temperatures. + FritzCall: + - new attribute `Path` for the class `Call` to access an + optional phone message. (#231) + FritzStatus: + - new method `get_avm_device_log()` to access system events. + Requires FritzOS 8. (#234) + - bugfix: in rare cases a session id of None has prevented a + successfull request of the http-interface. + - `fritzconnection.lib.fritzhomeauto.FritzHomeAutomation.device_informations()`, + deprecated in 1.9.0 + - `fritzconnection.lib.fritzstatus.FritzStatus.uptime()`, + deprecated in 1.9.0 + - `fritzconnection.lib.fritzwlan.FritzWLAN.channel_infos()`, + deprecated in 1.9.0 + +------------------------------------------------------------------- Old: ---- fritzconnection-1.14.0.tar.gz New: ---- fritzconnection-1.15.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-fritzconnection.spec ++++++ --- /var/tmp/diff_new_pack.0kQGSo/_old 2025-07-14 10:57:55.116093446 +0200 +++ /var/tmp/diff_new_pack.0kQGSo/_new 2025-07-14 10:57:55.132094109 +0200 @@ -18,7 +18,7 @@ %global pythons python3 Name: python-fritzconnection -Version: 1.14.0 +Version: 1.15.0 Release: 0 Summary: A Python module to talk to a AVM fritzbox License: MIT ++++++ fritzconnection-1.14.0.tar.gz -> fritzconnection-1.15.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/.gitignore new/fritzconnection-1.15.0/.gitignore --- old/fritzconnection-1.14.0/.gitignore 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/.gitignore 2025-05-17 16:21:25.000000000 +0200 @@ -16,7 +16,8 @@ bin build/* dist/* -docs/requirements.txt +docs/requirements.out +docs/requirements.local.txt fritzconnection.egg-info/* include lib_ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/.readthedocs.yml new/fritzconnection-1.15.0/.readthedocs.yml --- old/fritzconnection-1.14.0/.readthedocs.yml 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/.readthedocs.yml 2025-05-17 16:21:25.000000000 +0200 @@ -3,11 +3,13 @@ build: os: ubuntu-22.04 tools: - python: "3.11" + python: "3.12" python: install: - requirements: docs/requirements.txt + - method: pip + path: . sphinx: builder: html diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/CONTRIBUTING.md new/fritzconnection-1.15.0/CONTRIBUTING.md --- old/fritzconnection-1.14.0/CONTRIBUTING.md 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/CONTRIBUTING.md 2025-05-17 16:21:25.000000000 +0200 @@ -13,7 +13,9 @@ In general pull requests are welcome. Please create an issue first before putting too much work into a pull request that may not get merged at the end, for whatever reason. An issue can help to clarify points of view and motivation. -For pull requests there is a golden rule: **keep them small**. Smaller pull requests are easier to review and easier to merge – especially in cases when not every part of a larger changeset should get merged and has to get modifications. +In case of providing a pull-request please do this for the **development-branch** and not for the master-branch. + +For pull-requests there is a golden rule: **keep them small**. Smaller pull-requests are easier to review and easier to merge – especially in cases when not every part of a larger changeset should get merged and has to get modifications. Please avoid to just change the formatting. The result is most often nothing else than git-diff pollution. This is especially true for `black` (or `blue` or the corresponding modes in `ruff`) – this project startet before `black`. It is ok to use these tools for modified code snippets, but not for a module. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/docs/conf.py new/fritzconnection-1.15.0/docs/conf.py --- old/fritzconnection-1.14.0/docs/conf.py 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/docs/conf.py 2025-05-17 16:21:25.000000000 +0200 @@ -12,9 +12,9 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -# import os -# import sys -# sys.path.insert(0, os.path.abspath('..')) +import os +import sys +sys.path.insert(0, os.path.abspath('..')) # -- Project information ----------------------------------------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/docs/requirements.in new/fritzconnection-1.15.0/docs/requirements.in --- old/fritzconnection-1.14.0/docs/requirements.in 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/docs/requirements.in 1970-01-01 01:00:00.000000000 +0100 @@ -1,4 +0,0 @@ -Sphinx==5.1.1 -sphinx-rtd-theme==1.2.2 -fritzconnection -furo==2023.3.27 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/docs/requirements.local.in new/fritzconnection-1.15.0/docs/requirements.local.in --- old/fritzconnection-1.14.0/docs/requirements.local.in 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/docs/requirements.local.in 1970-01-01 01:00:00.000000000 +0100 @@ -1,3 +0,0 @@ -Sphinx==5.1.1 -sphinx-rtd-theme==1.2.2 -furo==2023.3.27 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/docs/requirements.txt new/fritzconnection-1.15.0/docs/requirements.txt --- old/fritzconnection-1.14.0/docs/requirements.txt 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/docs/requirements.txt 2025-05-17 16:21:25.000000000 +0200 @@ -1,69 +1,3 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --output-file=docs/requirements.txt --strip-extras docs/requirements.local.in -# -alabaster==0.7.16 - # via sphinx -babel==2.16.0 - # via sphinx -beautifulsoup4==4.12.3 - # via furo -certifi==2024.7.4 - # via requests -charset-normalizer==3.3.2 - # via requests -docutils==0.18.1 - # via - # sphinx - # sphinx-rtd-theme -furo==2023.3.27 - # via -r docs/requirements.local.in -idna==3.7 - # via requests -imagesize==1.4.1 - # via sphinx -jinja2==3.1.4 - # via sphinx -markupsafe==2.1.5 - # via jinja2 -packaging==24.1 - # via sphinx -pygments==2.18.0 - # via - # furo - # sphinx -requests==2.32.3 - # via sphinx -snowballstemmer==2.2.0 - # via sphinx -soupsieve==2.5 - # via beautifulsoup4 -sphinx==5.1.1 - # via - # -r docs/requirements.local.in - # furo - # sphinx-basic-ng - # sphinx-rtd-theme - # sphinxcontrib-jquery -sphinx-basic-ng==1.0.0b2 - # via furo -sphinx-rtd-theme==1.2.2 - # via -r docs/requirements.local.in -sphinxcontrib-applehelp==2.0.0 - # via sphinx -sphinxcontrib-devhelp==2.0.0 - # via sphinx -sphinxcontrib-htmlhelp==2.1.0 - # via sphinx -sphinxcontrib-jquery==4.1 - # via sphinx-rtd-theme -sphinxcontrib-jsmath==1.0.1 - # via sphinx -sphinxcontrib-qthelp==2.0.0 - # via sphinx -sphinxcontrib-serializinghtml==2.0.0 - # via sphinx -urllib3==2.2.2 - # via requests +sphinx<7.4.0 +furo<2025.0.0 +#fritzconnection diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/docs/sources/version_history.rst new/fritzconnection-1.15.0/docs/sources/version_history.rst --- old/fritzconnection-1.14.0/docs/sources/version_history.rst 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/docs/sources/version_history.rst 2025-05-17 16:21:25.000000000 +0200 @@ -4,6 +4,32 @@ =============== +1.15.0 - 2025-05-17 +------------------- + +- support added for Python 3.14 +- FritzConnection: + + - additional argument `redact_debug_log` (default: False) for optional redacting response in debug output. (#238) (#241) + - new method `get_cpu_temperatures()` providing a list of the last recent cpu-temperatures. (Rewrite of #232). + +- FritzCall: + + - new attribute `Path` for the class `Call` to access an optional phone message. (#231) + +- FritzStatus: + + - new method `get_avm_device_log()` to access system events. Requires FritzOS 8. (#234) + +- bugfix: in rare cases a session id of None has prevented a successfull request of the http-interface. + +- **removed**: + + - `fritzconnection.lib.fritzhomeauto.FritzHomeAutomation.device_informations()`, deprecated in 1.9.0 + - `fritzconnection.lib.fritzstatus.FritzStatus.uptime()`, deprecated in 1.9.0 + - `fritzconnection.lib.fritzwlan.FritzWLAN.channel_infos()`, deprecated in 1.9.0 + + 1.14.0 - 2024-08-12 ------------------- @@ -23,7 +49,7 @@ - bugfix: some devices may not return system-information the propper way, causing errors on the cli output. In these cases the system-information will get ignored. (#214) - documentation: some typos corrected. (#202, #204) - testing: `tox.ini` removed because of change to `nox`. Change from `pylint` to `ruff` for linting. -- deprecation: use of the `json` cache-format is discouraged. Use the default pickle-format instead. The highly dynamic TR-064 parser may get an ouverhaul in the future and to reduce the complexity of the parser the support of `json` for caching will be removed. +- **deprecation**: use of the `json` cache-format is discouraged. Use the default pickle-format instead. The highly dynamic TR-064 parser may get an ouverhaul in the future and to reduce the complexity of the parser the support of `json` for caching will be removed. 1.13.2 - 2023-09-17 @@ -52,7 +78,7 @@ - command-line interface: the check for a given password has been removed and substituted by a meaningfull error message in case of an authorization failure. Not every service is password-protected and passwords can optional provided by the environment. Therefore there is no need to require a password at cli level. (Motivated by #192) - bugfix: unneeded required password removed from the `fritzstatus` command-line interface. (#192) - bugfix: make `fritzwlan.get_beacon_security()` work with older router models not supporting the `NewX_AVM-DE_PossibleBeaconTypes` argument. (#191) -- deprecation: `fritzconnection.lib.fritzphonebook.list_phonebooks()` +- **deprecation**: `fritzconnection.lib.fritzphonebook.list_phonebooks()` 1.12.2 - 2023-07-09 @@ -96,7 +122,7 @@ - bugfix: create new socket on lost connection. (#179) -- Deprecations: +- **Deprecations**: - `fritzconnection.lib.fritzhomeauto.FritzHomeAutomation.device_information()` @@ -186,7 +212,8 @@ - New method `channel_info()` (#131) - FritzHomeAutomation: New method `device_information()` (#131) -- Deprecations: + +- **Deprecations**: - `fritzconnection.lib.fritzhomeauto.FritzHomeAutomation.device_informations()` - `fritzconnection.lib.fritzstatus.FritzStatus.uptime()` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/fritzconnection/__init__.py new/fritzconnection-1.15.0/fritzconnection/__init__.py --- old/fritzconnection-1.14.0/fritzconnection/__init__.py 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/fritzconnection/__init__.py 2025-05-17 16:21:25.000000000 +0200 @@ -13,7 +13,7 @@ # unused shortcut import are intended: # ruff: noqa: F401 -__version__ = "1.14.0" +__version__ = "1.15.0" # import shortcuts from .core.fritzconnection import FritzConnection diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/fritzconnection/core/fritzconnection.py new/fritzconnection-1.15.0/fritzconnection/core/fritzconnection.py --- old/fritzconnection-1.14.0/fritzconnection/core/fritzconnection.py 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/fritzconnection/core/fritzconnection.py 2025-05-17 16:21:25.000000000 +0200 @@ -170,6 +170,12 @@ (json|pickle) and FRITZ_CACHEDIRECTORY (a path). .. versionadded:: 1.10 + + `redact_debug_log` accepts a boolean for enabling redacting sensitiv + data (i.e. phone numbers) in debug outputs. Default is `False`. + + .. versionadded:: 1.15 + """ def __init__( @@ -186,6 +192,7 @@ cache_format: str | None = None, pool_connections: int = DEFAULT_POOL_CONNECTIONS, pool_maxsize: int = DEFAULT_POOL_MAXSIZE, + redact_debug_log: bool = False ): """ Initialisation of FritzConnection: reads all data from the box @@ -237,6 +244,9 @@ `pool_connections` and `pool_maxsize` accept integers for changing the default urllib3 settings in order to modify the number of reusable connections. + + `redact_debug_log` accepts a boolean for enabling redacting some + sensitiv data in debug outputs. Default is `False`. """ if address is None: address = FRITZ_IP_ADDRESS @@ -279,7 +289,7 @@ self.port = port self.soaper = Soaper( - address, port, user, password, timeout=timeout, session=session + address, port, user, password, timeout=timeout, session=session, redact_debug_log=redact_debug_log ) self.device_manager = DeviceManager(timeout=timeout, session=session) self._load_router_api( @@ -512,6 +522,18 @@ """ self.call_action("DeviceConfig1", "Reboot") + def get_cpu_temperatures(self) -> list[int]: + """ + Returns a list of the last measured cpu-temperatures. + The most recent entry is the first one in the list. + NOTE: this function call is experimental as it is based on a + non-public API. + """ + url = f"{self.http_interface.router_url}/query.lua" + payload = {"CPUTEMP": "cpu:status/StatTemperature"} + response = self.http_interface.call_url(url, payload) + return list(map(int, response.json()["CPUTEMP"].split(","))) + # ------------------------------------------- # internal methods to load router-api: # ------------------------------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/fritzconnection/core/fritzhttp.py new/fritzconnection-1.15.0/fritzconnection/core/fritzhttp.py --- old/fritzconnection-1.14.0/fritzconnection/core/fritzhttp.py 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/fritzconnection/core/fritzhttp.py 2025-05-17 16:21:25.000000000 +0200 @@ -54,14 +54,19 @@ return HTTP_PORT @property + def router_url(self): + """Returns the combination of router address and port.""" + return f"{self.fc.address}:{self.remote_port}" + + @property def login_url(self): """The login-url including protocol and configurable port.""" - return f"{self.fc.address}:{self.remote_port}{URL_LOGIN}" + return f"{self.router_url}{URL_LOGIN}" @property def homeauto_url(self): """The homeauto-url including protocol and configurable port.""" - return f"{self.fc.address}:{self.remote_port}{URL_HOMEAUTOSWITCH}" + return f"{self.router_url}{URL_HOMEAUTOSWITCH}" def execute(self, command=None, identifier=None, **kwargs): """ @@ -78,13 +83,29 @@ """ payload = {"switchcmd": command, "ain": identifier} payload.update(kwargs) + response = self.call_url(self.homeauto_url, payload) + return response.headers.get('content-type'), response.text + + def call_url(self, url, payload): + """ + Makes a call to the router with the provided url. Returns the + request object in case of success. Otherwise a + FritzHttpInterfaceError will get raised. + + Beside the public API documented by AVM this method allows calls + to undocumented APIs serving the router web-interface or + providing other data. + + WARNING: For a reliable application it is highly discouraged to + use undocumented endpoints because they can change any time without + notice. So an application may not survive a router OS update. + """ for sid in self._get_sid(): payload['sid'] = sid - with self.fc.session.get( - self.homeauto_url, params=payload - ) as response: + with self.fc.session.get(url, params=payload) as response: if response.status_code == HTTPStatus.OK: - return response.headers.get('content-type'), response.text + return response + msg = f"Request failed: http error code '{response.status_code}'" if response.status_code == HTTPStatus.FORBIDDEN: # can happen if FritzConnection was initialized @@ -102,6 +123,9 @@ failed. This can happen on an invalide or expired sid. In this case the sid gets regenerated for the second try. """ + if self.sid is None: + # a session id of None can lead to irritation + self._set_sid_from_box() yield self.sid self._set_sid_from_box() yield self.sid diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/fritzconnection/core/soaper.py new/fritzconnection-1.15.0/fritzconnection/core/soaper.py --- old/fritzconnection-1.14.0/fritzconnection/core/soaper.py 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/fritzconnection/core/soaper.py 2025-05-17 16:21:25.000000000 +0200 @@ -8,6 +8,7 @@ import datetime +from logging import DEBUG import html import re @@ -190,6 +191,22 @@ exception = FRITZ_ERRORS.get(error_code, FritzConnectionException) raise exception(message) +def redact_response(redact: bool, input: str): + # avoid expansive regex matching, when not neccessary + if fritzlogger.level != DEBUG or not redact: + return input + # redact possible phone numbers + # numbers with len of 5 or more, sourounded by white spaces or bracket + redacted = re.sub(r"([\s\[\(])\d{5,}([\s\]\)])", r"\1******\2", input) + + # redact external ip addresses + ext_ip_keys = 'New(ExternalIPAddress|ExternalIPv6Address)' + redacted = re.sub(r"(<{0}>)(.*)(</{0}>)".format(ext_ip_keys), r"\1******\4", redacted) + + # redact wifi passwords + wifi_pwd_keys = 'New(WEPKey\d+|PreSharedKey|KeyPassphrase)' + redacted = re.sub(r"(<{0}>)(.*)(</{0}>)".format(wifi_pwd_keys), r"\1******\4", redacted) + return redacted class Soaper: """ @@ -238,13 +255,14 @@ "ui4": int, } - def __init__(self, address, port, user, password, timeout=None, session=None): + def __init__(self, address, port, user, password, timeout=None, session=None, redact_debug_log=False): self.address = address self.port = port self.user = user self.password = password self.timeout = timeout self.session = session + self.redact_debug_log = redact_debug_log def get_body(self, service, action_name, arguments): """Returns the body by template substitution.""" @@ -263,7 +281,7 @@ def handle_response(response): fritzlogger.debug(f"response status: {response.status_code}") - fritzlogger.debug(response.text) + fritzlogger.debug(redact_response(self.redact_debug_log, response.text)) if response.status_code != 200: raise_fritzconnection_error(response) return self.parse_response(response, service, action_name) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/fritzconnection/lib/fritzcall.py new/fritzconnection-1.15.0/fritzconnection/lib/fritzcall.py --- old/fritzconnection-1.14.0/fritzconnection/lib/fritzcall.py 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/fritzconnection/lib/fritzcall.py 2025-05-17 16:21:25.000000000 +0200 @@ -184,8 +184,9 @@ """ Represents a call with the attributes provided by AVM. Instance attributes are *Id*, *Type*, *Called*, *Caller*, *CallerNumber*, - *CalledNumber*, *Name*, *Device*, *Port*, *Date*, *Duration* and - *Count*. The spelling represents the original xml-node names. + *CalledNumber*, *Name*, *Device*, *Port*, *Date*, *Duration*, + *Count* and *Path* which can be set in case of an optional phone + message. The spelling represents the original xml-node names. Additionally, the following attributes can be accessed by lowercase names: *id* returning the Id as integer, *type* returning the Type as integer, *date* returning the Date as datetime-instance, @@ -209,6 +210,7 @@ self.Date = None self.Duration = None self.Count = None + self.Path = None def __str__(self): number = self.Called if self.type == 3 else self.Caller diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/fritzconnection/lib/fritzhomeauto.py new/fritzconnection-1.15.0/fritzconnection/lib/fritzhomeauto.py --- old/fritzconnection-1.14.0/fritzconnection/lib/fritzhomeauto.py 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/fritzconnection/lib/fritzhomeauto.py 2025-05-17 16:21:25.000000000 +0200 @@ -96,14 +96,6 @@ """ return self._action('GetSpecificDeviceInfos', NewAIN=identifier) - def device_informations(self) -> list[dict]: - """ - .. deprecated:: 1.9.0 - Use :func:`get_device_information_list` instead. - """ - warn('This method is deprecated. Use "get_device_information_list" instead.', DeprecationWarning) - return self.get_device_information_list() - def device_information(self) -> list[dict]: """ .. deprecated:: 1.12.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/fritzconnection/lib/fritzstatus.py new/fritzconnection-1.15.0/fritzconnection/lib/fritzstatus.py --- old/fritzconnection-1.14.0/fritzconnection/lib/fritzstatus.py 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/fritzconnection/lib/fritzstatus.py 2025-05-17 16:21:25.000000000 +0200 @@ -2,11 +2,18 @@ Module to read status-information from an AVM FritzBox. """ - from __future__ import annotations +import datetime from collections import namedtuple -from warnings import warn + +from fritzconnection.core.processor import ( + Storage, + InstanceAttributeFactory, + processor, + process_node, +) +from fritzconnection.core.utils import get_xml_root from .fritzbase import AbstractLibraryBase from .fritztools import ( @@ -17,10 +24,10 @@ ) DefaultConnectionService = namedtuple( - "DefaultConnectionService", - "prefix connection_service postfix" + "DefaultConnectionService", "prefix connection_service postfix" ) + def _integer_or_original(value): """ Tries to convert value to an integer. Returns this integer on @@ -32,6 +39,44 @@ return value +@processor +class Event: + """ + Represents an AVM DeviceLog entry with the subnodes given as + class-attributes. The default types are strings as extracted from + the xml-source. + """ + + id = None + group = None + date = None + time = None + msg = None + + @property + def datetime(self): + return datetime.datetime.strptime(f"{self.date}{self.time}", "%d.%m.%y%H:%M:%S") + + +class DeviceLog(Storage): + """ + The AVM DeviceLog is a list of Event-nodes stored in the `events` + instance attribute. But instances of DeviceLog are also iterables + for the events. + """ + + Event = InstanceAttributeFactory(Event) + + def __init__(self, root): + self.events = list() + super().__init__(self.events) + process_node(self, root) + + def __iter__(self): + for event in self.events: + yield event + + class FritzStatus(AbstractLibraryBase): """ Class for requesting status-information: up, down, ip, activity @@ -107,15 +152,6 @@ return status["NewUptime"] @property - def uptime(self) -> int: - """ - .. deprecated:: 1.9.0 - Use :func:`connection_uptime` instead. - """ - warn('This method is deprecated. Use "connection_uptime" instead.', DeprecationWarning) - return self.connection_uptime - - @property def device_uptime(self) -> int: """Device uptime in seconds.""" status = self.fc.call_action("DeviceInfo1", "GetInfo") @@ -124,7 +160,7 @@ @property def str_uptime(self) -> str: """Connection uptime in human-readable format.""" - mins, secs = divmod(self.uptime, 60) + mins, secs = divmod(self.connection_uptime, 60) hours, mins = divmod(mins, 60) return "%02d:%02d:%02d" % (hours, mins, secs) @@ -139,9 +175,7 @@ value = status["NewX_AVM_DE_TotalBytesSent64"] except KeyError: # fallback for older FritzOS Versions not providing a 64 bit value: - status = self.fc.call_action( - "WANCommonIFC1", "GetTotalBytesSent" - ) + status = self.fc.call_action("WANCommonIFC1", "GetTotalBytesSent") value = status["NewTotalBytesSent"] return _integer_or_original(value) @@ -156,9 +190,7 @@ value = status["NewX_AVM_DE_TotalBytesReceived64"] except KeyError: # fallback for older FritzOS Versions not providing a 64 bit value: - status = self.fc.call_action( - "WANCommonIFC1", "GetTotalBytesReceived" - ) + status = self.fc.call_action("WANCommonIFC1", "GetTotalBytesReceived") value = status["NewTotalBytesReceived"] return _integer_or_original(value) @@ -324,10 +356,7 @@ # check for the corresponding action # whether mesh is supported try: - return ( - "X_AVM-DE_GetMeshListPath" - in self.fc.services["Hosts1"].actions - ) + return "X_AVM-DE_GetMeshListPath" in self.fc.services["Hosts1"].actions except KeyError: # can happen if "Hosts1" is not known return False @@ -345,6 +374,43 @@ """ return ArgumentNamespace(self.fc.call_action("DeviceInfo1", "GetInfo")) + def get_avm_device_log(self, filter: str | None = None) -> DeviceLog: + """ + The avm device log is a list of events with the attributes `id`, + `group`, `date`, `time` and `msg` holding information like "DSL + synchronization starting (training)" and other system messages. + The Method returns a DeviceLog instance holding a list of Event + instances. The DeviceLog instance is an iterable and can be + used on a FritzStatus instance like: + + >>> device_log = fritzstatus.get_avm_device_log() + >>> for event in device_log: + >>> print(event.datetime, event.msg) + + The returned events can be filtered by groups like 'sys', 'net', + 'fon', 'wlan' or 'usb'. To filter by a group provide the + group-name as filter-argument. + """ + result = self.fc.call_action("DeviceInfo1", "X_AVM-DE_GetDeviceLogPath") + path = result["NewDeviceLogPath"] + if filter: + path = f"{path}&filter={filter}" + url = f"{self.fc.address}:{self.fc.port}{path}" + root_node = get_xml_root(url, session=self.fc.session) + return DeviceLog(root_node) + + def get_cpu_temperatures(self) -> list[int]: + """ + Returns a list of the last measured cpu-temperatures (Celsius). + The most recent entry is the first one in the list. + NOTE: this function call is experimental as it is based on a + non-public API. + """ + url = f"{self.fc.http_interface.router_url}/query.lua" + payload = {"CPUTEMP": "cpu:status/StatTemperature"} + response = self.fc.http_interface.call_url(url, payload) + return list(map(int, response.json()["CPUTEMP"].split(","))) + def get_default_connection_service(self) -> DefaultConnectionService: """ Returns a namedtuple of type DefaultConnectionService: @@ -352,14 +418,11 @@ `device_connection` -> str (like "WANPPPConnection") `postfix` -> str """ - result = self.fc.call_action( - "Layer3Forwarding1", "GetDefaultConnectionService" - ) - prefix, connection_service, postfix = \ - result["NewDefaultConnectionService"].split('.', 2) - return DefaultConnectionService( - prefix, connection_service, postfix - ) + result = self.fc.call_action("Layer3Forwarding1", "GetDefaultConnectionService") + prefix, connection_service, postfix = result[ + "NewDefaultConnectionService" + ].split(".", 2) + return DefaultConnectionService(prefix, connection_service, postfix) @property def connection_service(self) -> str: @@ -388,7 +451,8 @@ @property def has_wan_support(self) -> bool: """ - True is the device supports a WAN interface. + True if the device supports a WAN interface. False otherwise. """ return "Layer3Forwarding1" in self.fc.services + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/fritzconnection/lib/fritzwlan.py new/fritzconnection-1.15.0/fritzconnection/lib/fritzwlan.py --- old/fritzconnection-1.14.0/fritzconnection/lib/fritzwlan.py 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/fritzconnection/lib/fritzwlan.py 2025-05-17 16:21:25.000000000 +0200 @@ -13,7 +13,6 @@ import itertools import random import string -from warnings import warn from ..core.exceptions import FritzServiceError from .fritzbase import AbstractLibraryBase @@ -208,14 +207,6 @@ """Alternative channels (as string)""" return self.channel_info()['NewPossibleChannels'] - def channel_infos(self) -> dict: - """ - .. deprecated:: 1.9.0 - Use :func:`channel_info` instead. - """ - warn('This method is deprecated. Use "channel_info" instead.', DeprecationWarning) - return self.channel_info() - def channel_info(self) -> dict: """ Return a dictionary with the keys *NewChannel* and diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/fritzconnection/tests/test_soaper.py new/fritzconnection-1.15.0/fritzconnection/tests/test_soaper.py --- old/fritzconnection-1.14.0/fritzconnection/tests/test_soaper.py 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/fritzconnection/tests/test_soaper.py 2025-05-17 16:21:25.000000000 +0200 @@ -30,6 +30,7 @@ get_html_safe_value, is_html_response, raise_fritzconnection_error, + redact_response, remove_html_tags, ) @@ -286,3 +287,146 @@ def test_get_converted_value_fails(data_type, value): with pytest.raises(ValueError): get_converted_value(data_type, value) + +def test_redact_debug_log_phone_numbers(): + response = """ + <?xml version="1.0"?> + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> + <s:Body> + <u:GetInfoResponse xmlns:u="urn:dslforum-org:service:DeviceInfo:1"> + <NewManufacturerName>AVM</NewManufacturerName> + <NewManufacturerOUI>00040E</NewManufacturerOUI> + <NewModelName>FRITZ!Box 7530 AX</NewModelName> + <NewDescription>FRITZ!Box 7530 AX Release 256.08.00</NewDescription> + <NewProductClass>FRITZ!Box</NewProductClass> + <NewSerialNumber>aabbccddeeff</NewSerialNumber> + <NewSoftwareVersion>256.08.00</NewSoftwareVersion> + <NewHardwareVersion>FRITZ!Box 7530 AX</NewHardwareVersion> + <NewSpecVersion>1.0</NewSpecVersion> + <NewProvisioningCode></NewProvisioningCode> + <NewUpTime>86446</NewUpTime> + <NewDeviceLog> + 23.11.24 12:28:10 Anmeldung der Internetrufnummer 491234567890 war nicht erfolgreich. Ursache: DNS-Fehler + 23.11.24 12:28:10 Anmeldung der Internetrufnummer 491234567891 war nicht erfolgreich. Ursache: DNS-Fehler + 23.11.24 12:28:10 Anmeldung der Internetrufnummer 491234567892 war nicht erfolgreich. Ursache: DNS-Fehler + 23.11.24 12:28:10 Anmeldung der Internetrufnummer 491234567893 war nicht erfolgreich. Ursache: DNS-Fehler + </u:GetInfoResponse> + </s:Body> + </s:Envelope> + """ + + result = redact_response(False, response) + assert result == response + + result = redact_response(True, response) + assert result == """ + <?xml version="1.0"?> + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> + <s:Body> + <u:GetInfoResponse xmlns:u="urn:dslforum-org:service:DeviceInfo:1"> + <NewManufacturerName>AVM</NewManufacturerName> + <NewManufacturerOUI>00040E</NewManufacturerOUI> + <NewModelName>FRITZ!Box 7530 AX</NewModelName> + <NewDescription>FRITZ!Box 7530 AX Release 256.08.00</NewDescription> + <NewProductClass>FRITZ!Box</NewProductClass> + <NewSerialNumber>aabbccddeeff</NewSerialNumber> + <NewSoftwareVersion>256.08.00</NewSoftwareVersion> + <NewHardwareVersion>FRITZ!Box 7530 AX</NewHardwareVersion> + <NewSpecVersion>1.0</NewSpecVersion> + <NewProvisioningCode></NewProvisioningCode> + <NewUpTime>86446</NewUpTime> + <NewDeviceLog> + 23.11.24 12:28:10 Anmeldung der Internetrufnummer ****** war nicht erfolgreich. Ursache: DNS-Fehler + 23.11.24 12:28:10 Anmeldung der Internetrufnummer ****** war nicht erfolgreich. Ursache: DNS-Fehler + 23.11.24 12:28:10 Anmeldung der Internetrufnummer ****** war nicht erfolgreich. Ursache: DNS-Fehler + 23.11.24 12:28:10 Anmeldung der Internetrufnummer ****** war nicht erfolgreich. Ursache: DNS-Fehler + </u:GetInfoResponse> + </s:Body> + </s:Envelope> + """ + +def test_redact_debug_log_external_ip_addresses(): + response = """ + <?xml version="1.0" encoding="utf-8"?> + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> + <s:Body> + <u:GetExternalIPAddressResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1"> + <NewExternalIPAddress>12.34.56.78</NewExternalIPAddress> + </u:GetExternalIPAddressResponse> + </s:Body> + </s:Envelope> + <?xml version="1.0" encoding="utf-8"?> + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> + <s:Body> + <u:X_AVM_DE_GetExternalIPv6AddressResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1"> + <NewExternalIPv6Address>0011:2233:4455:6677::0abcd</NewExternalIPv6Address> + <NewPrefixLength>64</NewPrefixLength> + <NewValidLifetime>0</NewValidLifetime> + <NewPreferedLifetime>0</NewPreferedLifetime> + </u:X_AVM_DE_GetExternalIPv6AddressResponse> + </s:Body> + </s:Envelope> + """ + + result = redact_response(False, response) + assert result == response + + result = redact_response(True, response) + assert result == """ + <?xml version="1.0" encoding="utf-8"?> + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> + <s:Body> + <u:GetExternalIPAddressResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1"> + <NewExternalIPAddress>******</NewExternalIPAddress> + </u:GetExternalIPAddressResponse> + </s:Body> + </s:Envelope> + <?xml version="1.0" encoding="utf-8"?> + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> + <s:Body> + <u:X_AVM_DE_GetExternalIPv6AddressResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1"> + <NewExternalIPv6Address>******</NewExternalIPv6Address> + <NewPrefixLength>64</NewPrefixLength> + <NewValidLifetime>0</NewValidLifetime> + <NewPreferedLifetime>0</NewPreferedLifetime> + </u:X_AVM_DE_GetExternalIPv6AddressResponse> + </s:Body> + </s:Envelope> + """ + +def test_redact_debug_log_wifi_passwords(): + response = """ + <?xml version="1.0"?> + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> + <s:Body> + <u:GetSecurityKeysResponse xmlns:u="urn:dslforum-org:service:WLANConfiguration:3"> + <NewWEPKey0>0123456789</NewWEPKey0> + <NewWEPKey1></NewWEPKey1> + <NewWEPKey2>01234 6789</NewWEPKey2> + <NewWEPKey3></NewWEPKey3> + <NewPreSharedKey>0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF</NewPreSharedKey> + <NewKeyPassphrase>MY_GREAT_WIFI_PSK</NewKeyPassphrase> + </u:GetSecurityKeysResponse> + </s:Body> + </s:Envelope> + """ + + result = redact_response(False, response) + assert result == response + + result = redact_response(True, response) + assert result == """ + <?xml version="1.0"?> + <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> + <s:Body> + <u:GetSecurityKeysResponse xmlns:u="urn:dslforum-org:service:WLANConfiguration:3"> + <NewWEPKey0>******</NewWEPKey0> + <NewWEPKey1>******</NewWEPKey1> + <NewWEPKey2>******</NewWEPKey2> + <NewWEPKey3>******</NewWEPKey3> + <NewPreSharedKey>******</NewPreSharedKey> + <NewKeyPassphrase>******</NewKeyPassphrase> + </u:GetSecurityKeysResponse> + </s:Body> + </s:Envelope> + """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/noxfile.py new/fritzconnection-1.15.0/noxfile.py --- old/fritzconnection-1.14.0/noxfile.py 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/noxfile.py 2025-05-17 16:21:25.000000000 +0200 @@ -1,7 +1,7 @@ import nox -PYTHON_TEST_VERSIONS = ("3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13") +PYTHON_TEST_VERSIONS = ("3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14") PYTHON_DEVELOPMENT_VERSION = "3.11" @@ -48,10 +48,11 @@ "pip-compile", "--strip-extras", "-q", - "--output-file=docs/requirements.txt", - "docs/requirements.local.in" + "--output-file=docs/requirements.out", + "docs/requirements.txt" + #"docs/requirements.local.in" ) - session.install("-r", "docs/requirements.txt") + session.install("-r", "docs/requirements.out") session.run("sphinx-build", "docs", "docs_out") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fritzconnection-1.14.0/setup.py new/fritzconnection-1.15.0/setup.py --- old/fritzconnection-1.14.0/setup.py 2024-08-12 17:58:36.000000000 +0200 +++ new/fritzconnection-1.15.0/setup.py 2025-05-17 16:21:25.000000000 +0200 @@ -44,6 +44,7 @@ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Software Development :: Libraries :: Python Modules", ], keywords="AVM FRITZ!Box fritzbox fritz TR-064 AHA-HTTP homeautomation",