`http.client.HTTPSConnection._tunnel` returning None: string (as how it has been) or null (proper None)?

In this issue, in Python 3.14 and 3.15, an AttributeError can be raised when an invalid value (such as None) is passed and is not handled properly.

As a solution, it could be handled differently depending on the desired outcome:

  1. Converting None to the string "None": keeps the previous behavior.
  2. Treating None as null (just skip to the next iteration): follows industry standards.
  3. Raising a clear error such as “this does not accept None values”: it would still fail, but with a more helpful message.

In my opinion, it’s important to preserve the current behavior to avoid breaking changes for existing users. That said, since Python is a language built for the future, it would be more appropriate to follow industry standards in the long run.

Option 1 benefits people who already depend on the current behavior, while option 2 benefits future users. Out of respect for current users, we could emit a DeprecationWarning announcing the change and make it effective after a transition period. That way, everyone is happy.

What do you think about it?

1 Like

I would personally advocate avoiding what breaks people now vs. when compared with 3.10

This is a result of late adoption of type hint enforcement. A type checker will have told anyone that the headers parameter can’t be anything other than Mapping[str, str] | None.

Setup Mypy or another type character and that will give your “3” for every modern library where this could be a problem: A helpful message about incorrect types at the relevant location.

So, to be clear, you’re using a private API (as indicated by the leading underscore) and passing an invalid value to it? And you think that it would be worthwhile for that API to protect against such usage?

There should be no existing users outside of stdlib code that uses the private method in strictly limited ways. In particular, all callers should be passing valid data.

We’ve just had a huge series of discussions on enforcing privacy, which were triggered by the core devs being frustrated that people continue to use private APIs, and expect stability guarantees from them. My position in that discussion, and also here, is that we should simply be more willing to say “no” to people expecting guarantees that we don’t provide. With that in mind, I’d strongly suggest that we do nothing about the existing method.

If we want to add a new, supported form of the API (probably just a supported headers method to replace _headers), then let’s do that. But please can we not continue muddying the waters by providing support for use of unsupported methods?

3 Likes

I don’t think so - the bug is traced back to a private API, but there’s a code example on the tracker issue that uses only public APIs. Though it’s possible that that comment is an LLM hallucination and I have not checked it.

2 Likes

Ah, sorry, I missed that. In which case, assuming it’s not an LLM hallucination, there’s certainly some value in fixing the issue. But if that’s the case, the description here is very misleading, as it certainly suggests that the OP wants a change to a private method.

Also, I’m not clear why this warrants being a discussion here, rather than just being on the tracker, if that’s the case.

1 Like

It’s also curious that the test script reported by the OP (in their second post) that @Rusuav links to doesn’t work as advertised for me

Script details

3.14.4 and Python 3.13.2:

import http.client

conn = http.client.HTTPSConnection("127.0.0.1", 8080)

conn.set_tunnel("example.com", 443, headers={"X-Test-Header": None})

try:
    conn.connect()
except AttributeError as e:
    print(f"Caught expected AttributeError: {e}")

traceback:

Traceback (most recent call last):
  File "<python-input-0>", line 8, in <module>
    conn.connect()
    ~~~~~~~~~~~~^^
  File "/usr/lib/python3.14/http/client.py", line 1501, in connect
    super().connect()
    ~~~~~~~~~~~~~~~^^
  File "/usr/lib/python3.14/http/client.py", line 1032, in connect
    self.sock = self._create_connection(
                ~~~~~~~~~~~~~~~~~~~~~~~^
        (self.host,self.port), self.timeout, self.source_address)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.14/socket.py", line 870, in create_connection
    raise exceptions[0]
  File "/usr/lib/python3.14/socket.py", line 855, in create_connection
    sock.connect(sa)
    ~~~~~~~~~~~~^^^^
ConnectionRefusedError: [Errno 111] Connection refused

However someone else posted a testing script that uses thrift and I’m just going to trust that their code actually works without verifying it :hand_with_index_finger_and_thumb_crossed:

Yeah, I checked that this is reproducible with public APIs, but I agree that the issue title is misleading.

I don’t think it’s necessary either, but here we are. This should be moved to Core Development, if anything.

1 Like