From 04093e518e548bfd242b0fc8ee8d9cabc3b70f8c Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Wed, 22 Mar 2017 15:59:58 +0300 Subject: [PATCH] Update pyi-stubs for builtin `open`, `os.path`, `os.fsencode` and `os.fsdecode` functions due to PEP-519. Add env and unit tests for them. --- python/helpers/typeshed/stdlib/3/builtins.pyi | 11 +++-- .../helpers/typeshed/stdlib/3/os/__init__.pyi | 23 +++++++-- .../PathLikePassedToStdlibFunctions/a.py | 6 +-- .../typeshed/stdlib/3/builtins_test.py | 13 ++++- python/testData/typeshed/stdlib/3/os_test.py | 49 +++++++++++++++++++ 5 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 python/testData/typeshed/stdlib/3/os_test.py diff --git a/python/helpers/typeshed/stdlib/3/builtins.pyi b/python/helpers/typeshed/stdlib/3/builtins.pyi index 8eea01806262..cd7fa4258de9 100644 --- a/python/helpers/typeshed/stdlib/3/builtins.pyi +++ b/python/helpers/typeshed/stdlib/3/builtins.pyi @@ -5,7 +5,7 @@ from typing import ( Sequence, MutableSequence, Mapping, MutableMapping, Tuple, List, Any, Dict, Callable, Generic, Set, AbstractSet, FrozenSet, MutableSet, Sized, Reversible, SupportsInt, SupportsFloat, SupportsBytes, SupportsAbs, SupportsRound, IO, Union, ItemsView, KeysView, ValuesView, - ByteString, Optional + ByteString, Optional, AnyStr, ) from abc import abstractmethod, ABCMeta from types import TracebackType @@ -796,8 +796,13 @@ def next(i: Iterator[_T], default: _VT) -> Union[_T, _VT]: ... def oct(i: int) -> str: ... # TODO __index__ if sys.version_info >= (3, 6): - from pathlib import Path - def open(file: Union[str, bytes, int, Path], mode: str = 'r', buffering: int = -1, encoding: str = None, + # This class is to be exported as PathLike from os, + # but we define it here as _PathLike to avoid import cycle issues. + # See https://github.com/python/typeshed/pull/991#issuecomment-288160993 + class _PathLike(Generic[AnyStr]): + def __fspath__(self) -> AnyStr: ... + + def open(file: Union[str, bytes, int, _PathLike], mode: str = 'r', buffering: int = -1, encoding: str = None, errors: str = None, newline: str = None, closefd: bool = ...) -> IO[Any]: ... else: def open(file: Union[str, bytes, int], mode: str = 'r', buffering: int = -1, encoding: str = None, diff --git a/python/helpers/typeshed/stdlib/3/os/__init__.pyi b/python/helpers/typeshed/stdlib/3/os/__init__.pyi index ecf894e14c77..b1da9b18df8b 100644 --- a/python/helpers/typeshed/stdlib/3/os/__init__.pyi +++ b/python/helpers/typeshed/stdlib/3/os/__init__.pyi @@ -108,8 +108,7 @@ TMP_MAX = 0 # Undocumented, but used by tempfile # ----- os classes (structures) ----- if sys.version_info >= (3, 6): - class PathLike: - def __fspath__(self) -> AnyStr: ... + from builtins import _PathLike as PathLike # See comment in builtins if sys.version_info >= (3, 5): @@ -182,8 +181,24 @@ class statvfs_result: # Unix only f_namemax = 0 # ----- os function stubs ----- -def fsencode(filename: str) -> bytes: ... -def fsdecode(filename: bytes) -> str: ... +if sys.version_info >= (3, 6): + def fsencode(filename: Union[str, bytes, PathLike]) -> bytes: ... +else: + def fsencode(filename: Union[str, bytes]) -> bytes: ... + +if sys.version_info >= (3, 6): + def fsdecode(filename: Union[str, bytes, PathLike]) -> str: ... +else: + def fsdecode(filename: Union[str, bytes]) -> str: ... + +if sys.version_info >= (3, 6): + @overload + def fspath(path: str) -> str: ... + @overload + def fspath(path: bytes) -> bytes: ... + @overload + def fspath(path: PathLike) -> Any: ... + def get_exec_path(env: Optional[Mapping[str, str]] = ...) -> List[str]: ... # NOTE: get_exec_path(): returns List[bytes] when env not None def ctermid() -> str: ... # Unix only diff --git a/python/testData/inspections/PyTypeCheckerInspection/PathLikePassedToStdlibFunctions/a.py b/python/testData/inspections/PyTypeCheckerInspection/PathLikePassedToStdlibFunctions/a.py index 0ffcd3293650..6241db2c7177 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/PathLikePassedToStdlibFunctions/a.py +++ b/python/testData/inspections/PyTypeCheckerInspection/PathLikePassedToStdlibFunctions/a.py @@ -30,9 +30,9 @@ b = B() open(b) -os.fspath(b) # TODO fail -os.fsencode(b) # TODO fail -os.fsdecode(b) # TODO fail +os.fspath(b) +os.fsencode(b) +os.fsdecode(b) Path(b) PurePath(b) diff --git a/python/testData/typeshed/stdlib/3/builtins_test.py b/python/testData/typeshed/stdlib/3/builtins_test.py index e1d921273a5d..a7c62a583b37 100644 --- a/python/testData/typeshed/stdlib/3/builtins_test.py +++ b/python/testData/typeshed/stdlib/3/builtins_test.py @@ -8,4 +8,15 @@ def test_zip(): assert(list(zip([1], [2], [3], [4], [5], [6], [7])) == [(1, 2, 3, 4, 5, 6, 7)]) assert(list(zip([1], [2], [3], [4], [5], [6], [7], [8])) == [(1, 2, 3, 4, 5, 6, 7, 8)]) assert(list(zip([1], [2], [3], [4], [5], [6], [7], [8], [9])) == [(1, 2, 3, 4, 5, 6, 7, 8, 9)]) - assert(list(zip([1], [2], [3], [4], [5], [6], [7], [8], [10])) == [(1, 2, 3, 4, 5, 6, 7, 8, 10)]) \ No newline at end of file + assert(list(zip([1], [2], [3], [4], [5], [6], [7], [8], [10])) == [(1, 2, 3, 4, 5, 6, 7, 8, 10)]) + + +def test_open_path_like(): + import sys + + class A: + def __fspath__(self): + return sys.argv[0] + + with open(A()) as f: + assert f.name == sys.argv[0] \ No newline at end of file diff --git a/python/testData/typeshed/stdlib/3/os_test.py b/python/testData/typeshed/stdlib/3/os_test.py new file mode 100644 index 000000000000..d9e75bd236a9 --- /dev/null +++ b/python/testData/typeshed/stdlib/3/os_test.py @@ -0,0 +1,49 @@ +def test_fsencode_path_like(): + import os + + class A: + def __fspath__(self): + return "a.py" + + class B: + def __fspath__(self): + return b"a.py" + + assert os.fsencode("a.py") == b"a.py" + assert os.fsencode(b"a.py") == b"a.py" + assert os.fsencode(A()) == b"a.py" + assert os.fsencode(B()) == b"a.py" + + +def test_fsdecode_path_like(): + import os + + class A: + def __fspath__(self): + return "a.py" + + class B: + def __fspath__(self): + return b"a.py" + + assert os.fsdecode("a.py") == "a.py" + assert os.fsdecode(b"a.py") == "a.py" + assert os.fsdecode(A()) == "a.py" + assert os.fsdecode(B()) == "a.py" + + +def test_fspath(): + import os + + class A: + def __fspath__(self): + return "a.py" + + class B: + def __fspath__(self): + return b"a.py" + + assert os.fspath("a.py") == "a.py" + assert os.fspath(b"a.py") == b"a.py" + assert os.fspath(A()) == "a.py" + assert os.fspath(B()) == b"a.py" \ No newline at end of file