Make library py.typed#258
Conversation
There was a problem hiding this comment.
Pull request overview
This PR aims to make fritzconnection a typed package (PEP 561 via py.typed) and to tighten type-checking by running mypy in --strict mode, with broad annotation updates across core, library, and CLI modules.
Changes:
- Add
fritzconnection/py.typedmarker for PEP 561 typed-package support. - Update the nox mypy session to run mypy with
--strict. - Add/adjust typing annotations and small refactors across core/lib/cli modules to satisfy strict mypy.
Reviewed changes
Copilot reviewed 27 out of 28 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| noxfile.py | Runs mypy with --strict for selected modules/dirs. |
| fritzconnection/py.typed | Adds PEP 561 marker file for typed-package consumers. |
| fritzconnection/lib/fritzwlan.py | Adds typing and refactors optional segno import usage. |
| fritzconnection/lib/fritztopology.py | Adds typing/casts and hardens topology loading against unexpected JSON types. |
| fritzconnection/lib/fritztools.py | Adds typing to formatting utilities and ArgumentNamespace. |
| fritzconnection/lib/fritzstatus.py | Adds typing/casts and small refactors for mypy strictness. |
| fritzconnection/lib/fritzphonebook.py | Adds typing/casts and some defensive checks around phonebook loading. |
| fritzconnection/lib/fritzhosts.py | Adds typing/casts and annotates get_mesh_topology JSON parsing. |
| fritzconnection/lib/fritzhomeauto.py | Adds typing/casts and refactors XML parsing; introduces a new __getattr__. |
| fritzconnection/lib/fritzcall.py | Adds typing/casts and a guard for self.calls being None. |
| fritzconnection/lib/fritzbase.py | Adds typing for base class init and modelname. |
| fritzconnection/core/utils.py | Adds typing and makes localname() more robust to non-matching tags. |
| fritzconnection/core/soaper.py | Adds typing, improves some error handling, and renames some locals for clarity. |
| fritzconnection/core/processor.py | Adds typing across processors/serializers and simplifies some processing calls. |
| fritzconnection/core/logger.py | Adds typing to logger helper functions. |
| fritzconnection/core/fritzmonitor.py | Adds typing for queues/sockets and tightens some thread-state checks. |
| fritzconnection/core/fritzhttp.py | Adds typing and makes login challenge/SID parsing more defensive. |
| fritzconnection/core/fritzconnection.py | Adds typing/casts, adds assertions, and adjusts call_http header handling. |
| fritzconnection/core/devices.py | Adds typing for device manager APIs. |
| fritzconnection/cli/utils.py | Adds typing for CLI utility functions. |
| fritzconnection/cli/fritzwlan.py | Adds typing annotations for CLI entrypoints/handlers. |
| fritzconnection/cli/fritzstatus.py | Adds typing annotations for CLI entrypoints/handlers. |
| fritzconnection/cli/fritzphonebook.py | Adds typing annotations for CLI entrypoints/handlers. |
| fritzconnection/cli/fritzmonitor.py | Adds typing annotations for CLI entrypoints/handlers. |
| fritzconnection/cli/fritzinspection.py | Adds typing annotations and hardens related-state-variable lookup. |
| fritzconnection/cli/fritzhosts.py | Adds typing annotations for CLI entrypoints/handlers. |
| fritzconnection/cli/fritzhomeauto.py | Adds typing annotations for CLI entrypoints/handlers. |
| fritzconnection/cli/fritzcall.py | Adds typing annotations for CLI entrypoints/handlers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if header is None: | ||
| header = "" | ||
| content_type, charset = [item.strip() for item in header.split(";")] | ||
| # extract the encoding from the charset-information | ||
| encoding = charset.split("=")[-1].strip() |
There was a problem hiding this comment.
call_http() sets header to an empty string when the response is missing a Content-Type, but then unconditionally unpacks header.split(";") into two values. This will raise ValueError for missing/invalid headers and break call_http().
Handle absent or unexpected Content-Type formats defensively (e.g., default to text/plain + utf-8, or parse only if a charset is present) before unpacking.
| if header is None: | |
| header = "" | |
| content_type, charset = [item.strip() for item in header.split(";")] | |
| # extract the encoding from the charset-information | |
| encoding = charset.split("=")[-1].strip() | |
| content_type = "text/plain" | |
| encoding = "utf-8" | |
| if header: | |
| parts = [item.strip() for item in header.split(";") if item.strip()] | |
| if parts: | |
| content_type = parts[0] | |
| # extract the encoding from charset-information if available | |
| for part in parts[1:]: | |
| key, sep, value = part.partition("=") | |
| if sep and key.strip().lower() == "charset" and value.strip(): | |
| encoding = value.strip() | |
| break |
|
|
||
| def __repr__(self): | ||
| def __getattr__(self, name: str) -> Any: | ||
| return self.__dict__[name] |
There was a problem hiding this comment.
HomeAutomationDevice.__getattr__ currently returns self.__dict__[name]. When the attribute is truly missing, this raises KeyError instead of AttributeError, which can break normal Python attribute semantics (e.g., hasattr(), getattr(..., default), introspection, and exception handling).
If you keep __getattr__, it should raise AttributeError(name) when the key isn’t present (or remove __getattr__ entirely if it’s not needed).
| return self.__dict__[name] | |
| try: | |
| return self.__dict__[name] | |
| except KeyError as exc: | |
| raise AttributeError(name) from exc |
|
Please Simone, don't expect to get a PR with a lot of diffs spreaded over 28 files getting merged. Regardless whether this origins from Copilot or not. Even in times of AI I think it is still important to undestand PRs and don't take them as they are. So for example Copilot tries to correct the following code from 'lib/fritzhomeauto.py:234 ff': There is no such code in my branches (neither in master nor v2-* I can find this). Also this code would be super-useless (even in the Copilot-version) because And then there is this code from 'core/fritzconnection.p:510 ff' (according to the PR, because of all the diffs the line-numbers may be a bit different depending on the branches. However it seems to be in the Again I'm unable to find this code in my branches. Also the code provided by Copilot to extract the encoding from a response from the router is something I would label as "bloated code"; it works, yes. But there is also a context. My context is to know the router and in case the router returns malformed data, the processing should not be defensive returning something else, but should raise some Exception because of the malformed data. This is still low level. Defensive programming can set on top of this. Anyway, thanks for your contributions to this project. I appreciate this very much. |
|
Hi Klaus, I perfectly understand your position. If you agree, in the next days I'll provide a "typing" PR for each file. |
|
Hi Simone, please wait until version 2, because there will be plenty of changes (see branch "v2-development"). I have had another project where type hints have been very helpful, but I'm also a bit reluctand, because this is not always the case and using type hints in excess can make code harder to read and to understand. The following code is not hard to read but an example of something I dislike: The So in the end, follow the Zen of Python: Beautiful is better than ugly and readability counts :) |
Played 10 minutes with CoPilot and moved the library to py.typed :-)
I also updated noxfile.py to force mypy to run in "
--strict" mode.