Repository navigation
Outdated/Incorrect annotations for os.startfile #10991
Description
In my opinion, the annotations for os.startfile have major problems that have been overlooked for the past few years.
For reference, the current annotations in stdlib/os__init__.pyi
if sys.platform == "win32":
if sys.version_info >= (3, 8):
def startfile(path: StrOrBytesPath, operation: str | None = None) -> None: ...
else:
def startfile(filepath: StrOrBytesPath, operation: str | None = None) -> None: ...1. Renaming the first argument for filepath to path in >= 3.8
Is there reason why this change has been added? While the official docs list the argument name as path (docs), the CPython source code has the first argument as filepath (CPython/main), and firing up a REPL to check actual behavior mirrors the same:
>>> from os import startfile
>>> startfile(path='explorer')
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
startfile(path='explorer')
TypeError: startfile() missing required argument 'filepath' (pos 1)
To me, it looks like a case of incorrect documentation that has been transferred into type annotations without checking whether it mirrors runtime behaviour.
2. Allowing None for operation argument
This is another annotation that conflicts with runtime behaviour:
>>> from os import startfile
>>> startfile(filepath='explorer', operation=None)
Traceback (most recent call last):
File "<pyshell#3>", line 1, in <module>
startfile(filepath='explorer', operation=None)
TypeError: startfile() argument 2 must be str, not None
I'll be honest, I'm not 100% sure what the correct default value is. Maybe just the empty string "" since the logic itself sits in Windows' own ShellExecuteW?
3. Missing arguments from >= 3.10
os.startfile gained additional arguments in Python 3.10, but they are all missing from the type annotations.
def startfile(
filepath: StrOrBytesPath,
operation: str = "", # Can't be None during runtime
arguments: str = "", # Can't be None during runtime
cwd: StrOrBytesPath | None = None, # Can be None during runtime
show_cmd: int = 1 # Can't be None during runtime, 1 from CPython source
) -> None: ...Disclaimer
I've been investigating this issue from my limited view point. There could be historic reasons why some things are the way they are. That's why I'm opening this as an Issue open for discussion instead of a PR. If you or somebody you know have more insight on this topic, please do share.
My proposal
If there are no mistakes in my assumptions, I would suggest the following annotations for os.startfile:
if sys.platform == "win32":
if sys.version_info >= (3, 10):
def startfile(filepath: StrOrBytesPath, operation: str = "", arguments: str = "", cwd: StrOrBytesPath | None = None, show_cmd: int = 1) -> None: ...
else:
def startfile(filepath: StrOrBytesPath, operation: str = '') -> None: ...
Activity
srittau commentedon Nov 7, 2023
I haven't looked in depth at the suggested changed, but a few notes: Changing
filepath's name seems to have been an oversight. It was changed in #9815 using a script. Cc @AlexWaygood. Foroperationwe can just use...as default to signal that it's a complex/unknown default. Proper PR welcome!hauntsaninja commentedon Nov 7, 2023
Technically it looks like #9815 fixed the argument name, it's just that it only fixed it on Python 3.7
stdlib: Fix os.startfile
JelleZijlstra commentedon Nov 8, 2023
I was curious why stubtest didn't catch this, as the function has an Argument Clinic signature in CPython. It appears to be because
typeshed/tests/stubtest_allowlists/win32.txt
Line 30 in b36f3c5
AlexWaygood commentedon Nov 8, 2023
not on my machine!
Python 3.12.0 (tags/v3.12.0:0fb18b0, Oct 2 2023, 13:03:39) [MSC v.1935 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import posix Traceback (most recent call last): File "<stdin>", line 1, in <module> ModuleNotFoundError: No module named 'posix'Also, I believe that that allowlist entry should only allowlist
posixitself not existing in the stubs. If you wanted to allowlist everything inposixas well as allowlisting the module itself, I think you'd need something likeposix.*for an allowlist entry.AlexWaygood commentedon Nov 8, 2023
To be clear: on Windows, the
posixmodule does "exist"... it's just it's calledntinstead ofposixon Windows, for Reasons. But thentmodule shares the same source code as theposixmodule, it just goes by a different name on Windows builds.Jakob-Stadler commentedon Nov 8, 2023
From what I could gather during my research,
posixbecomesntsomewhere along the line.JelleZijlstra commentedon Nov 8, 2023
Oops, sorry. Then I don't know why stubtest doesn't catch this. Does
os.startfile.__text_signature__exist?AlexWaygood commentedon Nov 8, 2023
JelleZijlstra commentedon Nov 8, 2023
I see, so we may have to teach something to deal with these "
<unrepresentable>" defaults.JelleZijlstra commentedon Nov 9, 2023
Cross-referencing python/cpython#87233
stdlib: Fix os.startfile (python#10996)