From 667f4bd9b2b3aef23e7bfc2edb45e6ed83bb5771 Mon Sep 17 00:00:00 2001 From: nineteendo Date: Wed, 10 Apr 2024 22:33:06 +0200 Subject: [PATCH 1/7] Add `follow_symlinks` to `os.path.exists()` --- Doc/library/os.path.rst | 7 ++++-- Lib/genericpath.py | 3 ++- Lib/ntpath.py | 7 +++++- Lib/test/test_genericpath.py | 44 +++++++++++++++++++++--------------- 4 files changed, 39 insertions(+), 22 deletions(-) diff --git a/Doc/library/os.path.rst b/Doc/library/os.path.rst index ebeb3bb50b8b1f..5ae30907e45e3b 100644 --- a/Doc/library/os.path.rst +++ b/Doc/library/os.path.rst @@ -127,10 +127,11 @@ the :mod:`glob` module.) Accepts a :term:`path-like object`. -.. function:: exists(path) +.. function:: exists(path, *, follow_symlinks=True) Return ``True`` if *path* refers to an existing path or an open - file descriptor. Returns ``False`` for broken symbolic links. On + file descriptor. Returns ``False`` for broken symbolic links if + follow_symlinks is set, otherwise ``True``. On some platforms, this function may return ``False`` if permission is not granted to execute :func:`os.stat` on the requested file, even if the *path* physically exists. @@ -142,6 +143,8 @@ the :mod:`glob` module.) .. versionchanged:: 3.6 Accepts a :term:`path-like object`. + .. versionchanged:: 3.13 + Added the *follow_symlinks* parameter. .. function:: lexists(path) diff --git a/Lib/genericpath.py b/Lib/genericpath.py index ba7b0a13c7f81d..bbf81fef784717 100644 --- a/Lib/genericpath.py +++ b/Lib/genericpath.py @@ -14,7 +14,8 @@ # Does a path exist? # This is false for dangling symbolic links on systems that support them. def exists(path): - """Test whether a path exists. Returns False for broken symbolic links""" + """Test whether a path exists. Returns False for broken symbolic links + if follow_symlinks is set, otherwise True""" try: os.stat(path) except (OSError, ValueError): diff --git a/Lib/ntpath.py b/Lib/ntpath.py index f5d1a2195dd633..ed4149cc8b41e4 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -898,7 +898,12 @@ def commonpath(paths): from nt import _path_isdir as isdir from nt import _path_isfile as isfile from nt import _path_islink as islink - from nt import _path_exists as exists + from nt import _path_exists + + def exists(path, *, follow_symlinks=True): + """Test whether a path exists. Returns False for broken symbolic links + if follow_symlinks is set, otherwise True""" + return _path_exists(path) if follow_symlinks else lexists(path) except ImportError: # Use genericpath.* as imported above pass diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py index f407ee3caf154c..bbbf6bd2a08e88 100644 --- a/Lib/test/test_genericpath.py +++ b/Lib/test/test_genericpath.py @@ -128,31 +128,39 @@ def test_filetime(self): ) def test_exists(self): + def check_exists(filename, expected): + bfilename = os.fsencode(filename) + self.assertIs(self.pathmodule.exists(filename), expected) + self.assertIs(self.pathmodule.exists(bfilename), expected) + self.assertIs(self.pathmodule.exists(filename, follow_symlinks=True), + expected) + self.assertIs(self.pathmodule.exists(bfilename, follow_symlinks=True), + expected) + + def check_lexists(filename, expected): + bfilename = os.fsencode(filename) + self.assertIs(self.pathmodule.lexists(filename), expected) + self.assertIs(self.pathmodule.lexists(bfilename), expected) + self.assertIs(self.pathmodule.exists(filename, follow_symlinks=False), + expected) + self.assertIs(self.pathmodule.exists(bfilename, follow_symlinks=False), + expected) + filename = os_helper.TESTFN - bfilename = os.fsencode(filename) self.addCleanup(os_helper.unlink, filename) - self.assertIs(self.pathmodule.exists(filename), False) - self.assertIs(self.pathmodule.exists(bfilename), False) + check_exists(filename, False) + check_lexists(filename, False) create_file(filename) - self.assertIs(self.pathmodule.exists(filename), True) - self.assertIs(self.pathmodule.exists(bfilename), True) - - self.assertIs(self.pathmodule.exists(filename + '\udfff'), False) - self.assertIs(self.pathmodule.exists(bfilename + b'\xff'), False) - self.assertIs(self.pathmodule.exists(filename + '\x00'), False) - self.assertIs(self.pathmodule.exists(bfilename + b'\x00'), False) - - if self.pathmodule is not genericpath: - self.assertIs(self.pathmodule.lexists(filename), True) - self.assertIs(self.pathmodule.lexists(bfilename), True) + check_exists(filename, True) + check_exists(filename + '\udfff', False) + check_exists(filename + '\x00', False) - self.assertIs(self.pathmodule.lexists(filename + '\udfff'), False) - self.assertIs(self.pathmodule.lexists(bfilename + b'\xff'), False) - self.assertIs(self.pathmodule.lexists(filename + '\x00'), False) - self.assertIs(self.pathmodule.lexists(bfilename + b'\x00'), False) + check_lexists(filename, True) + check_lexists(filename + '\udfff', False) + check_lexists(filename + '\x00', False) @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") @unittest.skipIf(is_emscripten, "Emscripten pipe fds have no stat") From 64c5d3961b0192efd1e801cd8a801076f2d08638 Mon Sep 17 00:00:00 2001 From: nineteendo Date: Wed, 10 Apr 2024 22:37:22 +0200 Subject: [PATCH 2/7] make `follow_symlinks` italic --- Doc/library/os.path.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/os.path.rst b/Doc/library/os.path.rst index 5ae30907e45e3b..afd69606cc831d 100644 --- a/Doc/library/os.path.rst +++ b/Doc/library/os.path.rst @@ -131,7 +131,7 @@ the :mod:`glob` module.) Return ``True`` if *path* refers to an existing path or an open file descriptor. Returns ``False`` for broken symbolic links if - follow_symlinks is set, otherwise ``True``. On + *follow_symlinks* is set, otherwise ``True``. On some platforms, this function may return ``False`` if permission is not granted to execute :func:`os.stat` on the requested file, even if the *path* physically exists. From 6abda2e9a66a288396242d848914db55f05e8b22 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 10 Apr 2024 20:41:16 +0000 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2024-04-10-20-41-15.gh-issue-117705.2pDs7H.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-04-10-20-41-15.gh-issue-117705.2pDs7H.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-04-10-20-41-15.gh-issue-117705.2pDs7H.rst b/Misc/NEWS.d/next/Core and Builtins/2024-04-10-20-41-15.gh-issue-117705.2pDs7H.rst new file mode 100644 index 00000000000000..130bcb61c388d1 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-04-10-20-41-15.gh-issue-117705.2pDs7H.rst @@ -0,0 +1 @@ +Added the *follow_symlinks* parameter to :func:`os.path.exists`. From ccf1e28faab2d91ac383bdd7fd61dbfffab04cd7 Mon Sep 17 00:00:00 2001 From: nineteendo Date: Wed, 10 Apr 2024 22:45:49 +0200 Subject: [PATCH 4/7] Add `follow_symlinks` to `genericpath.exists` --- Lib/genericpath.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/genericpath.py b/Lib/genericpath.py index bbf81fef784717..f183dbf24c4363 100644 --- a/Lib/genericpath.py +++ b/Lib/genericpath.py @@ -13,11 +13,11 @@ # Does a path exist? # This is false for dangling symbolic links on systems that support them. -def exists(path): +def exists(path, *, follow_symlinks=True): """Test whether a path exists. Returns False for broken symbolic links if follow_symlinks is set, otherwise True""" try: - os.stat(path) + os.stat(path, follow_symlinks=follow_symlinks) except (OSError, ValueError): return False return True From 865de023306c29ae42143158be9c89bd37a2591e Mon Sep 17 00:00:00 2001 From: nineteendo Date: Wed, 10 Apr 2024 23:14:51 +0200 Subject: [PATCH 5/7] fix surrogate decoding --- Lib/test/test_genericpath.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py index bbbf6bd2a08e88..08209be3f6890f 100644 --- a/Lib/test/test_genericpath.py +++ b/Lib/test/test_genericpath.py @@ -147,6 +147,7 @@ def check_lexists(filename, expected): expected) filename = os_helper.TESTFN + bfilename = os.fsencode(filename) self.addCleanup(os_helper.unlink, filename) check_exists(filename, False) @@ -155,11 +156,13 @@ def check_lexists(filename, expected): create_file(filename) check_exists(filename, True) - check_exists(filename + '\udfff', False) + self.assertIs(self.pathmodule.exists(filename + '\udfff'), False) + self.assertIs(self.pathmodule.exists(bfilename + b'\xff'), False) check_exists(filename + '\x00', False) check_lexists(filename, True) - check_lexists(filename + '\udfff', False) + self.assertIs(self.pathmodule.lexists(filename + '\udfff'), False) + self.assertIs(self.pathmodule.lexists(bfilename + b'\xff'), False) check_lexists(filename + '\x00', False) @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") From 962897bfcfd86182b6d7fa8179866389e2e332da Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Thu, 11 Apr 2024 07:06:08 +0200 Subject: [PATCH 6/7] Update test_fast_paths_in_use appropriately --- Lib/test/test_ntpath.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index 31156130fcc747..66743aacca3494 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -1,3 +1,4 @@ +import genericpath import inspect import ntpath import os @@ -1113,8 +1114,7 @@ def test_fast_paths_in_use(self): self.assertFalse(inspect.isfunction(os.path.isfile)) self.assertTrue(os.path.islink is nt._path_islink) self.assertFalse(inspect.isfunction(os.path.islink)) - self.assertTrue(os.path.exists is nt._path_exists) - self.assertFalse(inspect.isfunction(os.path.exists)) + self.assertFalse(os.path.exists is genericpath.exists) @unittest.skipIf(os.name != 'nt', "Dev Drives only exist on Win32") def test_isdevdrive(self): From e6586551a9966e459ba20d314832640c1eabd606 Mon Sep 17 00:00:00 2001 From: nineteendo Date: Fri, 12 Apr 2024 09:51:09 +0200 Subject: [PATCH 7/7] Add c implementation Co-authored-by: Eryk Sun --- Lib/ntpath.py | 8 +-- Lib/test/test_ntpath.py | 6 +- Modules/clinic/posixmodule.c.h | 96 ++++++++++++++++++++++++++-- Modules/posixmodule.c | 112 +++++++++++++++++++++------------ 4 files changed, 167 insertions(+), 55 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index ed4149cc8b41e4..d8da88993e9a38 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -898,12 +898,8 @@ def commonpath(paths): from nt import _path_isdir as isdir from nt import _path_isfile as isfile from nt import _path_islink as islink - from nt import _path_exists - - def exists(path, *, follow_symlinks=True): - """Test whether a path exists. Returns False for broken symbolic links - if follow_symlinks is set, otherwise True""" - return _path_exists(path) if follow_symlinks else lexists(path) + from nt import _path_exists as exists + from nt import _path_lexists as lexists except ImportError: # Use genericpath.* as imported above pass diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index 66743aacca3494..28706298618e04 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -1,4 +1,3 @@ -import genericpath import inspect import ntpath import os @@ -1114,7 +1113,10 @@ def test_fast_paths_in_use(self): self.assertFalse(inspect.isfunction(os.path.isfile)) self.assertTrue(os.path.islink is nt._path_islink) self.assertFalse(inspect.isfunction(os.path.islink)) - self.assertFalse(os.path.exists is genericpath.exists) + self.assertTrue(os.path.exists is nt._path_exists) + self.assertFalse(inspect.isfunction(os.path.exists)) + self.assertTrue(os.path.lexists is nt._path_lexists) + self.assertFalse(inspect.isfunction(os.path.lexists)) @unittest.skipIf(os.name != 'nt', "Dev Drives only exist on Win32") def test_isdevdrive(self): diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 0398629e3c10ce..a0d9af2f61ee79 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -2133,16 +2133,23 @@ os__path_isfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObj #if defined(MS_WINDOWS) PyDoc_STRVAR(os__path_exists__doc__, -"_path_exists($module, /, path)\n" +"_path_exists($module, /, path, follow_symlinks=True)\n" "--\n" "\n" -"Test whether a path exists. Returns False for broken symbolic links"); +"Test whether a path exists. Returns False for broken symbolic links\n" +"\n" +" path\n" +" Path to be tested; can be a string, bytes object, a path-like object,\n" +" or an integer that references an open file descriptor.\n" +" follow_symlinks\n" +" If False, and the last element of the path is a symbolic link, do not\n" +" test the target of the symbolic link."); #define OS__PATH_EXISTS_METHODDEF \ {"_path_exists", _PyCFunction_CAST(os__path_exists), METH_FASTCALL|METH_KEYWORDS, os__path_exists__doc__}, static PyObject * -os__path_exists_impl(PyObject *module, PyObject *path); +os__path_exists_impl(PyObject *module, PyObject *path, int follow_symlinks); static PyObject * os__path_exists(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -2150,6 +2157,79 @@ os__path_exists(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObj PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + #define NUM_KEYWORDS 2 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(path), &_Py_ID(follow_symlinks), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"path", "follow_symlinks", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "_path_exists", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[2]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; + PyObject *path; + int follow_symlinks = 1; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 2, 0, argsbuf); + if (!args) { + goto exit; + } + path = args[0]; + if (!noptargs) { + goto skip_optional_pos; + } + follow_symlinks = PyObject_IsTrue(args[1]); + if (follow_symlinks < 0) { + goto exit; + } +skip_optional_pos: + return_value = os__path_exists_impl(module, path, follow_symlinks); + +exit: + return return_value; +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(os__path_lexists__doc__, +"_path_lexists($module, /, path)\n" +"--\n" +"\n" +"Test whether a path exists. Returns True for broken symbolic links\n" +"\n" +" path\n" +" Path to be tested; can be a string, bytes object, a path-like object,\n" +" or an integer that references an open file descriptor."); + +#define OS__PATH_LEXISTS_METHODDEF \ + {"_path_lexists", _PyCFunction_CAST(os__path_lexists), METH_FASTCALL|METH_KEYWORDS, os__path_lexists__doc__}, + +static PyObject * +os__path_lexists_impl(PyObject *module, PyObject *path); + +static PyObject * +os__path_lexists(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + #define NUM_KEYWORDS 1 static struct { PyGC_Head _this_is_not_used; @@ -2169,7 +2249,7 @@ os__path_exists(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObj static const char * const _keywords[] = {"path", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, - .fname = "_path_exists", + .fname = "_path_lexists", .kwtuple = KWTUPLE, }; #undef KWTUPLE @@ -2181,7 +2261,7 @@ os__path_exists(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObj goto exit; } path = args[0]; - return_value = os__path_exists_impl(module, path); + return_value = os__path_lexists_impl(module, path); exit: return return_value; @@ -12051,6 +12131,10 @@ os__supports_virtual_terminal(PyObject *module, PyObject *Py_UNUSED(ignored)) #define OS__PATH_EXISTS_METHODDEF #endif /* !defined(OS__PATH_EXISTS_METHODDEF) */ +#ifndef OS__PATH_LEXISTS_METHODDEF + #define OS__PATH_LEXISTS_METHODDEF +#endif /* !defined(OS__PATH_LEXISTS_METHODDEF) */ + #ifndef OS__PATH_ISLINK_METHODDEF #define OS__PATH_ISLINK_METHODDEF #endif /* !defined(OS__PATH_ISLINK_METHODDEF) */ @@ -12602,4 +12686,4 @@ os__supports_virtual_terminal(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #define OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #endif /* !defined(OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF) */ -/*[clinic end generated code: output=511f0788a6b90db0 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=3cfe2a1ba37c496d input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 5e54cf64cd563e..fa1fc51d55f836 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -5284,21 +5284,24 @@ os__path_isfile_impl(PyObject *module, PyObject *path) os._path_exists path: 'O' + Path to be tested; can be a string, bytes object, a path-like object, + or an integer that references an open file descriptor. + + follow_symlinks: bool = True + If False, and the last element of the path is a symbolic link, do not + test the target of the symbolic link. Test whether a path exists. Returns False for broken symbolic links [clinic start generated code]*/ static PyObject * -os__path_exists_impl(PyObject *module, PyObject *path) -/*[clinic end generated code: output=f508c3b35e13a249 input=380f77cdfa0f7ae8]*/ +os__path_exists_impl(PyObject *module, PyObject *path, int follow_symlinks) +/*[clinic end generated code: output=f31130f636d45dcc input=26779f82ae9d4f5a]*/ { - HANDLE hfile; - BOOL close_file = TRUE; path_t _path = PATH_T_INITIALIZE("exists", "path", 0, 1); - int result; - BOOL slow_path = TRUE; - FILE_STAT_BASIC_INFORMATION statInfo; + HANDLE hfile; + int result = 0; if (!path_converter(path, &_path)) { path_cleanup(&_path); @@ -5310,49 +5313,56 @@ os__path_exists_impl(PyObject *module, PyObject *path) } Py_BEGIN_ALLOW_THREADS - if (_path.wide) { + if (_path.fd != -1) { + hfile = _Py_get_osfhandle_noraise(_path.fd); + if (hfile != INVALID_HANDLE_VALUE) { + result = 1; + } + } + else if (_path.wide) { + BOOL slow_path = TRUE; + FILE_STAT_BASIC_INFORMATION statInfo; if (_Py_GetFileInformationByName(_path.wide, FileStatBasicByNameInfo, - &statInfo, sizeof(statInfo))) { - if (!(statInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { + &statInfo, sizeof(statInfo))) + { + if (!(statInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) || + !follow_symlinks && + IsReparseTagNameSurrogate(statInfo.ReparseTag)) + { slow_path = FALSE; result = 1; } - } else if (_Py_GetFileInformationByName_ErrorIsTrustworthy(GetLastError())) { - slow_path = FALSE; - result = 0; - } - } - if (slow_path) { - if (_path.fd != -1) { - hfile = _Py_get_osfhandle_noraise(_path.fd); - close_file = FALSE; - } - else { - hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL, - OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); - } - if (hfile != INVALID_HANDLE_VALUE) { - result = 1; - if (close_file) { - CloseHandle(hfile); - } + } else if (_Py_GetFileInformationByName_ErrorIsTrustworthy( + GetLastError())) + { + slow_path = FALSE; } - else { + if (slow_path) { STRUCT_STAT st; - switch (GetLastError()) { - case ERROR_ACCESS_DENIED: - case ERROR_SHARING_VIOLATION: - case ERROR_CANT_ACCESS_FILE: - case ERROR_INVALID_PARAMETER: - if (STAT(_path.wide, &st)) { - result = 0; + if (!follow_symlinks) { + if (!LSTAT(_path.wide, &st)) { + result = 1; } - else { + } + else { + hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (hfile != INVALID_HANDLE_VALUE) { + CloseHandle(hfile); result = 1; } - break; - default: - result = 0; + else { + switch (GetLastError()) { + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + case ERROR_CANT_ACCESS_FILE: + case ERROR_INVALID_PARAMETER: + if (!STAT(_path.wide, &st)) { + result = 1; + } + } + } } } } @@ -5366,6 +5376,25 @@ os__path_exists_impl(PyObject *module, PyObject *path) } +/*[clinic input] +os._path_lexists + + path: 'O' + Path to be tested; can be a string, bytes object, a path-like object, + or an integer that references an open file descriptor. + +Test whether a path exists. Returns True for broken symbolic links + +[clinic start generated code]*/ + +static PyObject * +os__path_lexists_impl(PyObject *module, PyObject *path) +/*[clinic end generated code: output=b9a42a50b1df6651 input=96bc06e253ad0cd7]*/ +{ + return os__path_exists_impl(module, path, 0); +} + + /*[clinic input] os._path_islink @@ -16843,6 +16872,7 @@ static PyMethodDef posix_methods[] = { OS__PATH_ISFILE_METHODDEF OS__PATH_ISLINK_METHODDEF OS__PATH_EXISTS_METHODDEF + OS__PATH_LEXISTS_METHODDEF OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF {NULL, NULL} /* Sentinel */