Skip to content

Make library py.typed#258

Open
chemelli74 wants to merge 1 commit into
kbr:masterfrom
chemelli74:chemelli74-py.typed
Open

Make library py.typed#258
chemelli74 wants to merge 1 commit into
kbr:masterfrom
chemelli74:chemelli74-py.typed

Conversation

@chemelli74
Copy link
Copy Markdown
Contributor

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.

Copilot AI review requested due to automatic review settings April 6, 2026 19:13
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.typed marker 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.

Comment on lines +510 to 514
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()
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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

Copilot uses AI. Check for mistakes.

def __repr__(self):
def __getattr__(self, name: str) -> Any:
return self.__dict__[name]
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
return self.__dict__[name]
try:
return self.__dict__[name]
except KeyError as exc:
raise AttributeError(name) from exc

Copilot uses AI. Check for mistakes.
@kbr
Copy link
Copy Markdown
Owner

kbr commented Jun 2, 2026

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':

def __getattr__(self, name: str) -> Any:
    return self.__dict__[name]

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 __getattr__ is Python's last resort for lookup, therefore there can not be name in dict and an implementation of __getattr__ should do something else.

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 call_http() method.):

if header is None:
    header = ""        

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.

@chemelli74
Copy link
Copy Markdown
Contributor Author

chemelli74 commented Jun 2, 2026

Hi Klaus,

I perfectly understand your position.

If you agree, in the next days I'll provide a "typing" PR for each file.
Moving slowly but constantly towards the py.typed goal

@kbr
Copy link
Copy Markdown
Owner

kbr commented Jun 3, 2026

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:

def __init__(self, **kwargs: Any) -> None:

The __init__ method does not need a type hint for the return value but people often do it anyway and AI has learned it. Also Any on **kwargs may make mypy happy but nobody else. These hints are just more code to read but without any additional information. So add type hints when they are helpful and leave them when they are not. This is up to you and not to AI or the taste of other people likeing to follow inflexible rules.

So in the end, follow the Zen of Python: Beautiful is better than ugly and readability counts :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants