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