asyncio: allow port=None in create_server when a host is given - #16219
asyncio: allow port=None in create_server when a host is given#16219ekanshul wants to merge 1 commit into
port=None in create_server when a host is given#16219Conversation
create_server passes port straight to getaddrinfo, which accepts None, so create_server(proto, "localhost", None) works at runtime but the stub rejected it because the non-sock overload declared port: int. Widen that parameter to int | None in the six create_server overloads (three version gates each in base_events.pyi and events.pyi) and add test cases covering the host/port/sock combinations. create_connection also has port: int = ..., but it is left alone here: its port is not optional in the same way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Diff from mypy_primer, showing the effect of this PR on open source code: websockets (https://github.com/aaugustin/websockets)
+ src/websockets/asyncio/server.py:672: error: Unused "type: ignore" comment [unused-ignore]
aiohttp (https://github.com/aio-libs/aiohttp)
- aiohttp/web_runner.py:60:13: error: Argument 3 to "create_server" of "AbstractEventLoop" has incompatible type "int | None"; expected "int" [arg-type]
|
|
The mypy_primer run is a good confirmation of this one, so worth spelling out what the two lines are. aiohttp loses a real false positive. websockets is the same bug, already worked around in the source. The new "unused type: ignore" is that workaround becoming unnecessary: # mypy cannot tell that kwargs must provide sock when port is None.
return await loop.create_server(protocol_factory, host, port, **kwargs) # type: ignore[arg-type]So both entries point the same way: one project was eating the error, the other had suppressed it by hand. Worth noting for the second one that the comment there describes the stricter behaviour discussed in the issue, |
Fixes #16157.
create_serverhandsporttogetaddrinfo, which acceptsNone, soloop.create_server(proto, "localhost", None)works at runtime. The stub rejected it because the non-sockoverload declaredport: int. This widens it toport: int | None = Nonein the sixcreate_serveroverloads (three version gates each inbase_events.pyiandevents.pyi).create_connectionalso hasport: int = ..., but I left it alone, since itsportis not optional in the same way.What the runtime actually does
I probed every combination on 3.14.2 rather than going from the docs, since the report and the docs disagree in one place:
create_server(P)ValueError: Neither host/port nor sock were specifiedcreate_server(P, "localhost")create_server(P, "localhost", 8080)create_server(P, "localhost", None)create_server(P, None, None)ValueErrorcreate_server(P, None, 8080)create_server(P, sock=s)create_server(P, "localhost", sock=s)ValueError: host/port and sock can not be specified at the same timeWorth noting that the first line of the issue's example,
await loop.create_server(asyncio.Protocol), is listed there as succeeding but actually raisesValueError.On the third overload
@srittau suggested a third overload plus dropping the default on
portin the existing one. I could not write it exactly as sketched:hosthas a default andportdoes not, sohost: str | Sequence[str] | None = None, port: intis a syntax error, and droppinghost's default to fix that makes the overload requirehost, which rejectscreate_server(P, port=8080)(row 6 above, valid at runtime).Tightening the stub so the two
ValueErrorrows are rejected too therefore needs four overloads rather than three, across all six sites. That is a much larger diff and a real design call, so I have kept this PR to the reported bug, which it fixes with no new false positives. Happy to add the stricter version in this PR or a follow-up if you would like it.Tests
Added
stdlib/@tests/test_cases/asyncio/check_create_server.py, per the note in the issue that tests seemed useful here. It covers bothAbstractEventLoopandBaseEventLoop, and the twoValueErrorcombinations are pinned with# type: ignore.Verified locally:
main(4 errors, on exactly the twoport=Nonelines) and passes with the change.--stricton the test case passes for--python-version3.10 through 3.14, covering all three version gates.pyrightconfig.testcases.json, so both# type: ignores are necessary underreportUnnecessaryTypeIgnoreComment.asyncio.base_eventsandasyncio.eventson 3.13 and 3.14.check_typeshed_structure.py, black, flake8 with flake8-pyi, and the test-case ruff selection.