From ecf8d3f91642c263cfd90c76522adfbcb6bf445e Mon Sep 17 00:00:00 2001 From: Nineteendo Date: Wed, 22 May 2024 19:56:24 +0200 Subject: [PATCH 1/2] Speedup `os.path.isjunction` and `os.path.lexists` on Windows with a native implementation. --- Lib/ntpath.py | 22 ++-- Lib/test/test_ntpath.py | 4 + ...-05-08-18-33-07.gh-issue-118507.OCQsAY.rst | 1 + Modules/clinic/posixmodule.c.h | 106 +++++++++++++++++- Modules/posixmodule.c | 38 +++++++ 5 files changed, 156 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-05-08-18-33-07.gh-issue-118507.OCQsAY.rst diff --git a/Lib/ntpath.py b/Lib/ntpath.py index df3402d46c9cc6..3c1fc1f0d3beba 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -279,19 +279,11 @@ def dirname(p): # Is a path a junction? -if hasattr(os.stat_result, 'st_reparse_tag'): - def isjunction(path): - """Test whether a path is a junction""" - try: - st = os.lstat(path) - except (OSError, ValueError, AttributeError): - return False - return bool(st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT) -else: - def isjunction(path): - """Test whether a path is a junction""" - os.fspath(path) - return False +def isjunction(path): + """Test whether a path is a junction + Junctions are not supported on the current platform""" + os.fspath(path) + return False # Being true for dangling symbolic links is also useful. @@ -875,9 +867,11 @@ 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_isjunction as isjunction from nt import _path_exists as exists + from nt import _path_lexists as lexists except ImportError: - # Use genericpath.* as imported above + # Use Python version or genericpath.* as imported above pass diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index fff2b046b397b8..997490b2259854 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -1016,8 +1016,12 @@ 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.isjunction is nt._path_isjunction) + self.assertFalse(inspect.isfunction(os.path.isjunction)) 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/Misc/NEWS.d/next/Core and Builtins/2024-05-08-18-33-07.gh-issue-118507.OCQsAY.rst b/Misc/NEWS.d/next/Core and Builtins/2024-05-08-18-33-07.gh-issue-118507.OCQsAY.rst new file mode 100644 index 00000000000000..de1462f0d24fce --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-05-08-18-33-07.gh-issue-118507.OCQsAY.rst @@ -0,0 +1 @@ +Speedup :func:`os.path.isjunction` and :func:`os.path.lexists` on Windows with a native implementation. diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 829ebbcf438393..bb5bd324449ff0 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -2006,6 +2006,38 @@ os__path_exists(PyObject *module, PyObject *path) #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."); + +#define OS__PATH_LEXISTS_METHODDEF \ + {"_path_lexists", (PyCFunction)os__path_lexists, METH_O, os__path_lexists__doc__}, + +static int +os__path_lexists_impl(PyObject *module, PyObject *path); + +static PyObject * +os__path_lexists(PyObject *module, PyObject *path) +{ + PyObject *return_value = NULL; + int _return_value; + + _return_value = os__path_lexists_impl(module, path); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyBool_FromLong((long)_return_value); + +exit: + return return_value; +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + PyDoc_STRVAR(os__path_isdir__doc__, "_path_isdir($module, /, s)\n" "--\n" @@ -2196,6 +2228,70 @@ os__path_islink(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObj #endif /* defined(MS_WINDOWS) */ +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(os__path_isjunction__doc__, +"_path_isjunction($module, /, path)\n" +"--\n" +"\n" +"Test whether a path is a junction"); + +#define OS__PATH_ISJUNCTION_METHODDEF \ + {"_path_isjunction", _PyCFunction_CAST(os__path_isjunction), METH_FASTCALL|METH_KEYWORDS, os__path_isjunction__doc__}, + +static int +os__path_isjunction_impl(PyObject *module, PyObject *path); + +static PyObject * +os__path_isjunction(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; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(path), }, + }; + #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", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "_path_isjunction", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *path; + int _return_value; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + path = args[0]; + _return_value = os__path_isjunction_impl(module, path); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyBool_FromLong((long)_return_value); + +exit: + return return_value; +} + +#endif /* defined(MS_WINDOWS) */ + PyDoc_STRVAR(os__path_normpath__doc__, "_path_normpath($module, /, path)\n" "--\n" @@ -11464,6 +11560,10 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #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_ISDIR_METHODDEF #define OS__PATH_ISDIR_METHODDEF #endif /* !defined(OS__PATH_ISDIR_METHODDEF) */ @@ -11476,6 +11576,10 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #define OS__PATH_ISLINK_METHODDEF #endif /* !defined(OS__PATH_ISLINK_METHODDEF) */ +#ifndef OS__PATH_ISJUNCTION_METHODDEF + #define OS__PATH_ISJUNCTION_METHODDEF +#endif /* !defined(OS__PATH_ISJUNCTION_METHODDEF) */ + #ifndef OS_NICE_METHODDEF #define OS_NICE_METHODDEF #endif /* !defined(OS_NICE_METHODDEF) */ @@ -11987,4 +12091,4 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF #define OS_WAITSTATUS_TO_EXITCODE_METHODDEF #endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */ -/*[clinic end generated code: output=46e87bace3cc07b6 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=f2bc5f91048a6a84 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index b9fc6bd6fa6e5e..f132d92fee01a9 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -5165,6 +5165,25 @@ os__path_exists_impl(PyObject *module, PyObject *path) } +/*[clinic input] +os._path_lexists -> bool + + path: object + / + +Test whether a path exists. Returns True for broken symbolic links. + +[clinic start generated code]*/ + +static int +os__path_lexists_impl(PyObject *module, PyObject *path) +/*[clinic end generated code: output=fec4a91cf4ffccf1 input=8843d4d6d4e7c779]*/ +{ + path_t _path = PATH_T_INITIALIZE("_path_lexists", "path", 0, 1); + return _testFileExists(&_path, path, FALSE); +} + + /*[clinic input] os._path_isdir -> bool @@ -5218,6 +5237,23 @@ os__path_islink_impl(PyObject *module, PyObject *path) return _testFileType(&_path, path, PY_IFLNK); } +/*[clinic input] +os._path_isjunction -> bool + + path: object + +Test whether a path is a junction + +[clinic start generated code]*/ + +static int +os__path_isjunction_impl(PyObject *module, PyObject *path) +/*[clinic end generated code: output=f1d51682a077654d input=103ccedcdb714f11]*/ +{ + path_t _path = PATH_T_INITIALIZE("_path_isjunction", "path", 0, 1); + return _testFileType(&_path, path, PY_IFMNT); +} + #undef PY_IFREG #undef PY_IFDIR #undef PY_IFLNK @@ -16009,7 +16045,9 @@ static PyMethodDef posix_methods[] = { OS__PATH_ISDIR_METHODDEF OS__PATH_ISFILE_METHODDEF OS__PATH_ISLINK_METHODDEF + OS__PATH_ISJUNCTION_METHODDEF OS__PATH_EXISTS_METHODDEF + OS__PATH_LEXISTS_METHODDEF {NULL, NULL} /* Sentinel */ }; From b5319e101425bb53e4403271f20322ce53e207c4 Mon Sep 17 00:00:00 2001 From: Nice Zombies Date: Wed, 19 Jun 2024 13:18:49 +0200 Subject: [PATCH 2/2] Rename 2024-05-08-18-33-07.gh-issue-118507.OCQsAY.rst to 2024-05-08-18-33-07.gh-issue-118507.OCQsAY.rst --- .../2024-05-08-18-33-07.gh-issue-118507.OCQsAY.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/{Core and Builtins => Library}/2024-05-08-18-33-07.gh-issue-118507.OCQsAY.rst (100%) diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-05-08-18-33-07.gh-issue-118507.OCQsAY.rst b/Misc/NEWS.d/next/Library/2024-05-08-18-33-07.gh-issue-118507.OCQsAY.rst similarity index 100% rename from Misc/NEWS.d/next/Core and Builtins/2024-05-08-18-33-07.gh-issue-118507.OCQsAY.rst rename to Misc/NEWS.d/next/Library/2024-05-08-18-33-07.gh-issue-118507.OCQsAY.rst