mirror of https://github.com/python/cpython
gh-104803: Implement ntpath.isdevdrive for checking whether a path is on a Windows Dev Drive (GH-104805)
This commit is contained in:
parent
e92ac0a741
commit
bfd20d257e
|
@ -304,6 +304,24 @@ the :mod:`glob` module.)
|
|||
Accepts a :term:`path-like object`.
|
||||
|
||||
|
||||
.. function:: isdevdrive(path)
|
||||
|
||||
Return ``True`` if pathname *path* is located on a Windows Dev Drive.
|
||||
A Dev Drive is optimized for developer scenarios, and offers faster
|
||||
performance for reading and writing files. It is recommended for use for
|
||||
source code, temporary build directories, package caches, and other
|
||||
IO-intensive operations.
|
||||
|
||||
May raise an error for an invalid path, for example, one without a
|
||||
recognizable drive, but returns ``False`` on platforms that do not support
|
||||
Dev Drives. See `the Windows documentation <https://learn.microsoft.com/windows/dev-drive/>`_
|
||||
for information on enabling and creating Dev Drives.
|
||||
|
||||
.. availability:: Windows.
|
||||
|
||||
.. versionadded:: 3.12
|
||||
|
||||
|
||||
.. function:: join(path, *paths)
|
||||
|
||||
Join one or more path segments intelligently. The return value is the
|
||||
|
|
|
@ -867,3 +867,19 @@ try:
|
|||
except ImportError:
|
||||
# Use genericpath.* as imported above
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
from nt import _path_isdevdrive
|
||||
except ImportError:
|
||||
def isdevdrive(path):
|
||||
"""Determines whether the specified path is on a Windows Dev Drive."""
|
||||
# Never a Dev Drive
|
||||
return False
|
||||
else:
|
||||
def isdevdrive(path):
|
||||
"""Determines whether the specified path is on a Windows Dev Drive."""
|
||||
try:
|
||||
return _path_isdevdrive(abspath(path))
|
||||
except OSError:
|
||||
return False
|
||||
|
|
|
@ -992,6 +992,26 @@ class TestNtpath(NtpathTestCase):
|
|||
self.assertTrue(os.path.exists is nt._path_exists)
|
||||
self.assertFalse(inspect.isfunction(os.path.exists))
|
||||
|
||||
@unittest.skipIf(os.name != 'nt', "Dev Drives only exist on Win32")
|
||||
def test_isdevdrive(self):
|
||||
# Result may be True or False, but shouldn't raise
|
||||
self.assertIn(ntpath.isdevdrive(os_helper.TESTFN), (True, False))
|
||||
# ntpath.isdevdrive can handle relative paths
|
||||
self.assertIn(ntpath.isdevdrive("."), (True, False))
|
||||
self.assertIn(ntpath.isdevdrive(b"."), (True, False))
|
||||
# Volume syntax is supported
|
||||
self.assertIn(ntpath.isdevdrive(os.listvolumes()[0]), (True, False))
|
||||
# Invalid volume returns False from os.path method
|
||||
self.assertFalse(ntpath.isdevdrive(r"\\?\Volume{00000000-0000-0000-0000-000000000000}\\"))
|
||||
# Invalid volume raises from underlying helper
|
||||
with self.assertRaises(OSError):
|
||||
nt._path_isdevdrive(r"\\?\Volume{00000000-0000-0000-0000-000000000000}\\")
|
||||
|
||||
@unittest.skipIf(os.name == 'nt', "isdevdrive fallback only used off Win32")
|
||||
def test_isdevdrive_fallback(self):
|
||||
# Fallback always returns False
|
||||
self.assertFalse(ntpath.isdevdrive(os_helper.TESTFN))
|
||||
|
||||
|
||||
class NtCommonTest(test_genericpath.CommonTest, unittest.TestCase):
|
||||
pathmodule = ntpath
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
Add :func:`os.path.isdevdrive` to detect whether a path is on a Windows Dev
|
||||
Drive. Returns ``False`` on platforms that do not support Dev Drive, and is
|
||||
absent on non-Windows platforms.
|
|
@ -1715,6 +1715,70 @@ exit:
|
|||
|
||||
#if defined(MS_WINDOWS)
|
||||
|
||||
PyDoc_STRVAR(os__path_isdevdrive__doc__,
|
||||
"_path_isdevdrive($module, /, path)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Determines whether the specified path is on a Windows Dev Drive.");
|
||||
|
||||
#define OS__PATH_ISDEVDRIVE_METHODDEF \
|
||||
{"_path_isdevdrive", _PyCFunction_CAST(os__path_isdevdrive), METH_FASTCALL|METH_KEYWORDS, os__path_isdevdrive__doc__},
|
||||
|
||||
static PyObject *
|
||||
os__path_isdevdrive_impl(PyObject *module, path_t *path);
|
||||
|
||||
static PyObject *
|
||||
os__path_isdevdrive(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_isdevdrive",
|
||||
.kwtuple = KWTUPLE,
|
||||
};
|
||||
#undef KWTUPLE
|
||||
PyObject *argsbuf[1];
|
||||
path_t path = PATH_T_INITIALIZE("_path_isdevdrive", "path", 0, 0);
|
||||
|
||||
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
|
||||
if (!args) {
|
||||
goto exit;
|
||||
}
|
||||
if (!path_converter(args[0], &path)) {
|
||||
goto exit;
|
||||
}
|
||||
return_value = os__path_isdevdrive_impl(module, &path);
|
||||
|
||||
exit:
|
||||
/* Cleanup for path */
|
||||
path_cleanup(&path);
|
||||
|
||||
return return_value;
|
||||
}
|
||||
|
||||
#endif /* defined(MS_WINDOWS) */
|
||||
|
||||
#if defined(MS_WINDOWS)
|
||||
|
||||
PyDoc_STRVAR(os__getfullpathname__doc__,
|
||||
"_getfullpathname($module, path, /)\n"
|
||||
"--\n"
|
||||
|
@ -11379,6 +11443,10 @@ exit:
|
|||
#define OS_LISTMOUNTS_METHODDEF
|
||||
#endif /* !defined(OS_LISTMOUNTS_METHODDEF) */
|
||||
|
||||
#ifndef OS__PATH_ISDEVDRIVE_METHODDEF
|
||||
#define OS__PATH_ISDEVDRIVE_METHODDEF
|
||||
#endif /* !defined(OS__PATH_ISDEVDRIVE_METHODDEF) */
|
||||
|
||||
#ifndef OS__GETFULLPATHNAME_METHODDEF
|
||||
#define OS__GETFULLPATHNAME_METHODDEF
|
||||
#endif /* !defined(OS__GETFULLPATHNAME_METHODDEF) */
|
||||
|
@ -11922,4 +11990,4 @@ exit:
|
|||
#ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF
|
||||
#define OS_WAITSTATUS_TO_EXITCODE_METHODDEF
|
||||
#endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */
|
||||
/*[clinic end generated code: output=47750e0e29c8d707 input=a9049054013a1b77]*/
|
||||
/*[clinic end generated code: output=9d8b0d6717c9af54 input=a9049054013a1b77]*/
|
||||
|
|
|
@ -4534,6 +4534,95 @@ exit:
|
|||
}
|
||||
|
||||
|
||||
/*[clinic input]
|
||||
os._path_isdevdrive
|
||||
|
||||
path: path_t
|
||||
|
||||
Determines whether the specified path is on a Windows Dev Drive.
|
||||
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
os__path_isdevdrive_impl(PyObject *module, path_t *path)
|
||||
/*[clinic end generated code: output=1f437ea6677433a2 input=ee83e4996a48e23d]*/
|
||||
{
|
||||
#ifndef PERSISTENT_VOLUME_STATE_DEV_VOLUME
|
||||
/* This flag will be documented at
|
||||
https://learn.microsoft.com/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_file_fs_persistent_volume_information
|
||||
after release, and will be available in the latest WinSDK.
|
||||
We include the flag to avoid a specific version dependency
|
||||
on the latest WinSDK. */
|
||||
const int PERSISTENT_VOLUME_STATE_DEV_VOLUME = 0x00002000;
|
||||
#endif
|
||||
int err = 0;
|
||||
PyObject *r = NULL;
|
||||
wchar_t volume[MAX_PATH];
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
if (!GetVolumePathNameW(path->wide, volume, MAX_PATH)) {
|
||||
/* invalid path of some kind */
|
||||
/* Note that this also includes the case where a volume is mounted
|
||||
in a path longer than 260 characters. This is likely to be rare
|
||||
and problematic for other reasons, so a (soft) failure in this
|
||||
check seems okay. */
|
||||
err = GetLastError();
|
||||
} else if (GetDriveTypeW(volume) != DRIVE_FIXED) {
|
||||
/* only care about local dev drives */
|
||||
r = Py_False;
|
||||
} else {
|
||||
HANDLE hVolume = CreateFileW(
|
||||
volume,
|
||||
FILE_READ_ATTRIBUTES,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS,
|
||||
NULL
|
||||
);
|
||||
if (hVolume == INVALID_HANDLE_VALUE) {
|
||||
err = GetLastError();
|
||||
} else {
|
||||
FILE_FS_PERSISTENT_VOLUME_INFORMATION volumeState = {0};
|
||||
volumeState.Version = 1;
|
||||
volumeState.FlagMask = PERSISTENT_VOLUME_STATE_DEV_VOLUME;
|
||||
if (!DeviceIoControl(
|
||||
hVolume,
|
||||
FSCTL_QUERY_PERSISTENT_VOLUME_STATE,
|
||||
&volumeState,
|
||||
sizeof(volumeState),
|
||||
&volumeState,
|
||||
sizeof(volumeState),
|
||||
NULL,
|
||||
NULL
|
||||
)) {
|
||||
err = GetLastError();
|
||||
}
|
||||
CloseHandle(hVolume);
|
||||
if (err == ERROR_INVALID_PARAMETER) {
|
||||
/* not supported on this platform */
|
||||
r = Py_False;
|
||||
} else if (!err) {
|
||||
r = (volumeState.VolumeFlags & PERSISTENT_VOLUME_STATE_DEV_VOLUME)
|
||||
? Py_True : Py_False;
|
||||
}
|
||||
}
|
||||
}
|
||||
Py_END_ALLOW_THREADS
|
||||
|
||||
if (err) {
|
||||
PyErr_SetFromWindowsErr(err);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (r) {
|
||||
return Py_NewRef(r);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
_PyOS_getfullpathname(const wchar_t *path, wchar_t **abspath_p)
|
||||
{
|
||||
|
@ -15805,6 +15894,7 @@ static PyMethodDef posix_methods[] = {
|
|||
OS_SETNS_METHODDEF
|
||||
OS_UNSHARE_METHODDEF
|
||||
|
||||
OS__PATH_ISDEVDRIVE_METHODDEF
|
||||
OS__PATH_ISDIR_METHODDEF
|
||||
OS__PATH_ISFILE_METHODDEF
|
||||
OS__PATH_ISLINK_METHODDEF
|
||||
|
|
Loading…
Reference in New Issue