ยปCore Development>Code coverage>Lib/ntpath.py

Python code coverage for Lib/ntpath.py

#countcontent
1n/a# Module 'ntpath' -- common operations on WinNT/Win95 pathnames
2n/a"""Common pathname manipulations, WindowsNT/95 version.
3n/a
4n/aInstead of importing this module directly, import os and refer to this
5n/amodule as os.path.
6n/a"""
7n/a
8n/aimport os
9n/aimport sys
10n/aimport stat
11n/aimport genericpath
12n/afrom genericpath import *
13n/a
14n/a__all__ = ["normcase","isabs","join","splitdrive","split","splitext",
15n/a "basename","dirname","commonprefix","getsize","getmtime",
16n/a "getatime","getctime", "islink","exists","lexists","isdir","isfile",
17n/a "ismount", "expanduser","expandvars","normpath","abspath",
18n/a "curdir","pardir","sep","pathsep","defpath","altsep",
19n/a "extsep","devnull","realpath","supports_unicode_filenames","relpath",
20n/a "samefile", "sameopenfile", "samestat", "commonpath"]
21n/a
22n/a# strings representing various path-related bits and pieces
23n/a# These are primarily for export; internally, they are hardcoded.
24n/acurdir = '.'
25n/apardir = '..'
26n/aextsep = '.'
27n/asep = '\\'
28n/apathsep = ';'
29n/aaltsep = '/'
30n/adefpath = '.;C:\\bin'
31n/adevnull = 'nul'
32n/a
33n/adef _get_bothseps(path):
34n/a if isinstance(path, bytes):
35n/a return b'\\/'
36n/a else:
37n/a return '\\/'
38n/a
39n/a# Normalize the case of a pathname and map slashes to backslashes.
40n/a# Other normalizations (such as optimizing '../' away) are not done
41n/a# (this is done by normpath).
42n/a
43n/adef normcase(s):
44n/a """Normalize case of pathname.
45n/a
46n/a Makes all characters lowercase and all slashes into backslashes."""
47n/a s = os.fspath(s)
48n/a try:
49n/a if isinstance(s, bytes):
50n/a return s.replace(b'/', b'\\').lower()
51n/a else:
52n/a return s.replace('/', '\\').lower()
53n/a except (TypeError, AttributeError):
54n/a if not isinstance(s, (bytes, str)):
55n/a raise TypeError("normcase() argument must be str or bytes, "
56n/a "not %r" % s.__class__.__name__) from None
57n/a raise
58n/a
59n/a
60n/a# Return whether a path is absolute.
61n/a# Trivial in Posix, harder on Windows.
62n/a# For Windows it is absolute if it starts with a slash or backslash (current
63n/a# volume), or if a pathname after the volume-letter-and-colon or UNC-resource
64n/a# starts with a slash or backslash.
65n/a
66n/adef isabs(s):
67n/a """Test whether a path is absolute"""
68n/a s = os.fspath(s)
69n/a s = splitdrive(s)[1]
70n/a return len(s) > 0 and s[0] in _get_bothseps(s)
71n/a
72n/a
73n/a# Join two (or more) paths.
74n/adef join(path, *paths):
75n/a path = os.fspath(path)
76n/a if isinstance(path, bytes):
77n/a sep = b'\\'
78n/a seps = b'\\/'
79n/a colon = b':'
80n/a else:
81n/a sep = '\\'
82n/a seps = '\\/'
83n/a colon = ':'
84n/a try:
85n/a if not paths:
86n/a path[:0] + sep #23780: Ensure compatible data type even if p is null.
87n/a result_drive, result_path = splitdrive(path)
88n/a for p in map(os.fspath, paths):
89n/a p_drive, p_path = splitdrive(p)
90n/a if p_path and p_path[0] in seps:
91n/a # Second path is absolute
92n/a if p_drive or not result_drive:
93n/a result_drive = p_drive
94n/a result_path = p_path
95n/a continue
96n/a elif p_drive and p_drive != result_drive:
97n/a if p_drive.lower() != result_drive.lower():
98n/a # Different drives => ignore the first path entirely
99n/a result_drive = p_drive
100n/a result_path = p_path
101n/a continue
102n/a # Same drive in different case
103n/a result_drive = p_drive
104n/a # Second path is relative to the first
105n/a if result_path and result_path[-1] not in seps:
106n/a result_path = result_path + sep
107n/a result_path = result_path + p_path
108n/a ## add separator between UNC and non-absolute path
109n/a if (result_path and result_path[0] not in seps and
110n/a result_drive and result_drive[-1:] != colon):
111n/a return result_drive + sep + result_path
112n/a return result_drive + result_path
113n/a except (TypeError, AttributeError, BytesWarning):
114n/a genericpath._check_arg_types('join', path, *paths)
115n/a raise
116n/a
117n/a
118n/a# Split a path in a drive specification (a drive letter followed by a
119n/a# colon) and the path specification.
120n/a# It is always true that drivespec + pathspec == p
121n/adef splitdrive(p):
122n/a """Split a pathname into drive/UNC sharepoint and relative path specifiers.
123n/a Returns a 2-tuple (drive_or_unc, path); either part may be empty.
124n/a
125n/a If you assign
126n/a result = splitdrive(p)
127n/a It is always true that:
128n/a result[0] + result[1] == p
129n/a
130n/a If the path contained a drive letter, drive_or_unc will contain everything
131n/a up to and including the colon. e.g. splitdrive("c:/dir") returns ("c:", "/dir")
132n/a
133n/a If the path contained a UNC path, the drive_or_unc will contain the host name
134n/a and share up to but not including the fourth directory separator character.
135n/a e.g. splitdrive("//host/computer/dir") returns ("//host/computer", "/dir")
136n/a
137n/a Paths cannot contain both a drive letter and a UNC path.
138n/a
139n/a """
140n/a p = os.fspath(p)
141n/a if len(p) >= 2:
142n/a if isinstance(p, bytes):
143n/a sep = b'\\'
144n/a altsep = b'/'
145n/a colon = b':'
146n/a else:
147n/a sep = '\\'
148n/a altsep = '/'
149n/a colon = ':'
150n/a normp = p.replace(altsep, sep)
151n/a if (normp[0:2] == sep*2) and (normp[2:3] != sep):
152n/a # is a UNC path:
153n/a # vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
154n/a # \\machine\mountpoint\directory\etc\...
155n/a # directory ^^^^^^^^^^^^^^^
156n/a index = normp.find(sep, 2)
157n/a if index == -1:
158n/a return p[:0], p
159n/a index2 = normp.find(sep, index + 1)
160n/a # a UNC path can't have two slashes in a row
161n/a # (after the initial two)
162n/a if index2 == index + 1:
163n/a return p[:0], p
164n/a if index2 == -1:
165n/a index2 = len(p)
166n/a return p[:index2], p[index2:]
167n/a if normp[1:2] == colon:
168n/a return p[:2], p[2:]
169n/a return p[:0], p
170n/a
171n/a
172n/a# Split a path in head (everything up to the last '/') and tail (the
173n/a# rest). After the trailing '/' is stripped, the invariant
174n/a# join(head, tail) == p holds.
175n/a# The resulting head won't end in '/' unless it is the root.
176n/a
177n/adef split(p):
178n/a """Split a pathname.
179n/a
180n/a Return tuple (head, tail) where tail is everything after the final slash.
181n/a Either part may be empty."""
182n/a p = os.fspath(p)
183n/a seps = _get_bothseps(p)
184n/a d, p = splitdrive(p)
185n/a # set i to index beyond p's last slash
186n/a i = len(p)
187n/a while i and p[i-1] not in seps:
188n/a i -= 1
189n/a head, tail = p[:i], p[i:] # now tail has no slashes
190n/a # remove trailing slashes from head, unless it's all slashes
191n/a head = head.rstrip(seps) or head
192n/a return d + head, tail
193n/a
194n/a
195n/a# Split a path in root and extension.
196n/a# The extension is everything starting at the last dot in the last
197n/a# pathname component; the root is everything before that.
198n/a# It is always true that root + ext == p.
199n/a
200n/adef splitext(p):
201n/a p = os.fspath(p)
202n/a if isinstance(p, bytes):
203n/a return genericpath._splitext(p, b'\\', b'/', b'.')
204n/a else:
205n/a return genericpath._splitext(p, '\\', '/', '.')
206n/asplitext.__doc__ = genericpath._splitext.__doc__
207n/a
208n/a
209n/a# Return the tail (basename) part of a path.
210n/a
211n/adef basename(p):
212n/a """Returns the final component of a pathname"""
213n/a return split(p)[1]
214n/a
215n/a
216n/a# Return the head (dirname) part of a path.
217n/a
218n/adef dirname(p):
219n/a """Returns the directory component of a pathname"""
220n/a return split(p)[0]
221n/a
222n/a# Is a path a symbolic link?
223n/a# This will always return false on systems where os.lstat doesn't exist.
224n/a
225n/adef islink(path):
226n/a """Test whether a path is a symbolic link.
227n/a This will always return false for Windows prior to 6.0.
228n/a """
229n/a try:
230n/a st = os.lstat(path)
231n/a except (OSError, AttributeError):
232n/a return False
233n/a return stat.S_ISLNK(st.st_mode)
234n/a
235n/a# Being true for dangling symbolic links is also useful.
236n/a
237n/adef lexists(path):
238n/a """Test whether a path exists. Returns True for broken symbolic links"""
239n/a try:
240n/a st = os.lstat(path)
241n/a except OSError:
242n/a return False
243n/a return True
244n/a
245n/a# Is a path a mount point?
246n/a# Any drive letter root (eg c:\)
247n/a# Any share UNC (eg \\server\share)
248n/a# Any volume mounted on a filesystem folder
249n/a#
250n/a# No one method detects all three situations. Historically we've lexically
251n/a# detected drive letter roots and share UNCs. The canonical approach to
252n/a# detecting mounted volumes (querying the reparse tag) fails for the most
253n/a# common case: drive letter roots. The alternative which uses GetVolumePathName
254n/a# fails if the drive letter is the result of a SUBST.
255n/atry:
256n/a from nt import _getvolumepathname
257n/aexcept ImportError:
258n/a _getvolumepathname = None
259n/adef ismount(path):
260n/a """Test whether a path is a mount point (a drive root, the root of a
261n/a share, or a mounted volume)"""
262n/a path = os.fspath(path)
263n/a seps = _get_bothseps(path)
264n/a path = abspath(path)
265n/a root, rest = splitdrive(path)
266n/a if root and root[0] in seps:
267n/a return (not rest) or (rest in seps)
268n/a if rest in seps:
269n/a return True
270n/a
271n/a if _getvolumepathname:
272n/a return path.rstrip(seps) == _getvolumepathname(path).rstrip(seps)
273n/a else:
274n/a return False
275n/a
276n/a
277n/a# Expand paths beginning with '~' or '~user'.
278n/a# '~' means $HOME; '~user' means that user's home directory.
279n/a# If the path doesn't begin with '~', or if the user or $HOME is unknown,
280n/a# the path is returned unchanged (leaving error reporting to whatever
281n/a# function is called with the expanded path as argument).
282n/a# See also module 'glob' for expansion of *, ? and [...] in pathnames.
283n/a# (A function should also be defined to do full *sh-style environment
284n/a# variable expansion.)
285n/a
286n/adef expanduser(path):
287n/a """Expand ~ and ~user constructs.
288n/a
289n/a If user or $HOME is unknown, do nothing."""
290n/a path = os.fspath(path)
291n/a if isinstance(path, bytes):
292n/a tilde = b'~'
293n/a else:
294n/a tilde = '~'
295n/a if not path.startswith(tilde):
296n/a return path
297n/a i, n = 1, len(path)
298n/a while i < n and path[i] not in _get_bothseps(path):
299n/a i += 1
300n/a
301n/a if 'HOME' in os.environ:
302n/a userhome = os.environ['HOME']
303n/a elif 'USERPROFILE' in os.environ:
304n/a userhome = os.environ['USERPROFILE']
305n/a elif not 'HOMEPATH' in os.environ:
306n/a return path
307n/a else:
308n/a try:
309n/a drive = os.environ['HOMEDRIVE']
310n/a except KeyError:
311n/a drive = ''
312n/a userhome = join(drive, os.environ['HOMEPATH'])
313n/a
314n/a if isinstance(path, bytes):
315n/a userhome = os.fsencode(userhome)
316n/a
317n/a if i != 1: #~user
318n/a userhome = join(dirname(userhome), path[1:i])
319n/a
320n/a return userhome + path[i:]
321n/a
322n/a
323n/a# Expand paths containing shell variable substitutions.
324n/a# The following rules apply:
325n/a# - no expansion within single quotes
326n/a# - '$$' is translated into '$'
327n/a# - '%%' is translated into '%' if '%%' are not seen in %var1%%var2%
328n/a# - ${varname} is accepted.
329n/a# - $varname is accepted.
330n/a# - %varname% is accepted.
331n/a# - varnames can be made out of letters, digits and the characters '_-'
332n/a# (though is not verified in the ${varname} and %varname% cases)
333n/a# XXX With COMMAND.COM you can use any characters in a variable name,
334n/a# XXX except '^|<>='.
335n/a
336n/adef expandvars(path):
337n/a """Expand shell variables of the forms $var, ${var} and %var%.
338n/a
339n/a Unknown variables are left unchanged."""
340n/a path = os.fspath(path)
341n/a if isinstance(path, bytes):
342n/a if b'$' not in path and b'%' not in path:
343n/a return path
344n/a import string
345n/a varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii')
346n/a quote = b'\''
347n/a percent = b'%'
348n/a brace = b'{'
349n/a rbrace = b'}'
350n/a dollar = b'$'
351n/a environ = getattr(os, 'environb', None)
352n/a else:
353n/a if '$' not in path and '%' not in path:
354n/a return path
355n/a import string
356n/a varchars = string.ascii_letters + string.digits + '_-'
357n/a quote = '\''
358n/a percent = '%'
359n/a brace = '{'
360n/a rbrace = '}'
361n/a dollar = '$'
362n/a environ = os.environ
363n/a res = path[:0]
364n/a index = 0
365n/a pathlen = len(path)
366n/a while index < pathlen:
367n/a c = path[index:index+1]
368n/a if c == quote: # no expansion within single quotes
369n/a path = path[index + 1:]
370n/a pathlen = len(path)
371n/a try:
372n/a index = path.index(c)
373n/a res += c + path[:index + 1]
374n/a except ValueError:
375n/a res += c + path
376n/a index = pathlen - 1
377n/a elif c == percent: # variable or '%'
378n/a if path[index + 1:index + 2] == percent:
379n/a res += c
380n/a index += 1
381n/a else:
382n/a path = path[index+1:]
383n/a pathlen = len(path)
384n/a try:
385n/a index = path.index(percent)
386n/a except ValueError:
387n/a res += percent + path
388n/a index = pathlen - 1
389n/a else:
390n/a var = path[:index]
391n/a try:
392n/a if environ is None:
393n/a value = os.fsencode(os.environ[os.fsdecode(var)])
394n/a else:
395n/a value = environ[var]
396n/a except KeyError:
397n/a value = percent + var + percent
398n/a res += value
399n/a elif c == dollar: # variable or '$$'
400n/a if path[index + 1:index + 2] == dollar:
401n/a res += c
402n/a index += 1
403n/a elif path[index + 1:index + 2] == brace:
404n/a path = path[index+2:]
405n/a pathlen = len(path)
406n/a try:
407n/a index = path.index(rbrace)
408n/a except ValueError:
409n/a res += dollar + brace + path
410n/a index = pathlen - 1
411n/a else:
412n/a var = path[:index]
413n/a try:
414n/a if environ is None:
415n/a value = os.fsencode(os.environ[os.fsdecode(var)])
416n/a else:
417n/a value = environ[var]
418n/a except KeyError:
419n/a value = dollar + brace + var + rbrace
420n/a res += value
421n/a else:
422n/a var = path[:0]
423n/a index += 1
424n/a c = path[index:index + 1]
425n/a while c and c in varchars:
426n/a var += c
427n/a index += 1
428n/a c = path[index:index + 1]
429n/a try:
430n/a if environ is None:
431n/a value = os.fsencode(os.environ[os.fsdecode(var)])
432n/a else:
433n/a value = environ[var]
434n/a except KeyError:
435n/a value = dollar + var
436n/a res += value
437n/a if c:
438n/a index -= 1
439n/a else:
440n/a res += c
441n/a index += 1
442n/a return res
443n/a
444n/a
445n/a# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.
446n/a# Previously, this function also truncated pathnames to 8+3 format,
447n/a# but as this module is called "ntpath", that's obviously wrong!
448n/a
449n/adef normpath(path):
450n/a """Normalize path, eliminating double slashes, etc."""
451n/a path = os.fspath(path)
452n/a if isinstance(path, bytes):
453n/a sep = b'\\'
454n/a altsep = b'/'
455n/a curdir = b'.'
456n/a pardir = b'..'
457n/a special_prefixes = (b'\\\\.\\', b'\\\\?\\')
458n/a else:
459n/a sep = '\\'
460n/a altsep = '/'
461n/a curdir = '.'
462n/a pardir = '..'
463n/a special_prefixes = ('\\\\.\\', '\\\\?\\')
464n/a if path.startswith(special_prefixes):
465n/a # in the case of paths with these prefixes:
466n/a # \\.\ -> device names
467n/a # \\?\ -> literal paths
468n/a # do not do any normalization, but return the path unchanged
469n/a return path
470n/a path = path.replace(altsep, sep)
471n/a prefix, path = splitdrive(path)
472n/a
473n/a # collapse initial backslashes
474n/a if path.startswith(sep):
475n/a prefix += sep
476n/a path = path.lstrip(sep)
477n/a
478n/a comps = path.split(sep)
479n/a i = 0
480n/a while i < len(comps):
481n/a if not comps[i] or comps[i] == curdir:
482n/a del comps[i]
483n/a elif comps[i] == pardir:
484n/a if i > 0 and comps[i-1] != pardir:
485n/a del comps[i-1:i+1]
486n/a i -= 1
487n/a elif i == 0 and prefix.endswith(sep):
488n/a del comps[i]
489n/a else:
490n/a i += 1
491n/a else:
492n/a i += 1
493n/a # If the path is now empty, substitute '.'
494n/a if not prefix and not comps:
495n/a comps.append(curdir)
496n/a return prefix + sep.join(comps)
497n/a
498n/a
499n/a# Return an absolute path.
500n/atry:
501n/a from nt import _getfullpathname
502n/a
503n/aexcept ImportError: # not running on Windows - mock up something sensible
504n/a def abspath(path):
505n/a """Return the absolute version of a path."""
506n/a path = os.fspath(path)
507n/a if not isabs(path):
508n/a if isinstance(path, bytes):
509n/a cwd = os.getcwdb()
510n/a else:
511n/a cwd = os.getcwd()
512n/a path = join(cwd, path)
513n/a return normpath(path)
514n/a
515n/aelse: # use native Windows method on Windows
516n/a def abspath(path):
517n/a """Return the absolute version of a path."""
518n/a
519n/a if path: # Empty path must return current working directory.
520n/a path = os.fspath(path)
521n/a try:
522n/a path = _getfullpathname(path)
523n/a except OSError:
524n/a pass # Bad path - return unchanged.
525n/a elif isinstance(path, bytes):
526n/a path = os.getcwdb()
527n/a else:
528n/a path = os.getcwd()
529n/a return normpath(path)
530n/a
531n/a# realpath is a no-op on systems without islink support
532n/arealpath = abspath
533n/a# Win9x family and earlier have no Unicode filename support.
534n/asupports_unicode_filenames = (hasattr(sys, "getwindowsversion") and
535n/a sys.getwindowsversion()[3] >= 2)
536n/a
537n/adef relpath(path, start=None):
538n/a """Return a relative version of a path"""
539n/a path = os.fspath(path)
540n/a if isinstance(path, bytes):
541n/a sep = b'\\'
542n/a curdir = b'.'
543n/a pardir = b'..'
544n/a else:
545n/a sep = '\\'
546n/a curdir = '.'
547n/a pardir = '..'
548n/a
549n/a if start is None:
550n/a start = curdir
551n/a
552n/a if not path:
553n/a raise ValueError("no path specified")
554n/a
555n/a start = os.fspath(start)
556n/a try:
557n/a start_abs = abspath(normpath(start))
558n/a path_abs = abspath(normpath(path))
559n/a start_drive, start_rest = splitdrive(start_abs)
560n/a path_drive, path_rest = splitdrive(path_abs)
561n/a if normcase(start_drive) != normcase(path_drive):
562n/a raise ValueError("path is on mount %r, start on mount %r" % (
563n/a path_drive, start_drive))
564n/a
565n/a start_list = [x for x in start_rest.split(sep) if x]
566n/a path_list = [x for x in path_rest.split(sep) if x]
567n/a # Work out how much of the filepath is shared by start and path.
568n/a i = 0
569n/a for e1, e2 in zip(start_list, path_list):
570n/a if normcase(e1) != normcase(e2):
571n/a break
572n/a i += 1
573n/a
574n/a rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
575n/a if not rel_list:
576n/a return curdir
577n/a return join(*rel_list)
578n/a except (TypeError, ValueError, AttributeError, BytesWarning, DeprecationWarning):
579n/a genericpath._check_arg_types('relpath', path, start)
580n/a raise
581n/a
582n/a
583n/a# Return the longest common sub-path of the sequence of paths given as input.
584n/a# The function is case-insensitive and 'separator-insensitive', i.e. if the
585n/a# only difference between two paths is the use of '\' versus '/' as separator,
586n/a# they are deemed to be equal.
587n/a#
588n/a# However, the returned path will have the standard '\' separator (even if the
589n/a# given paths had the alternative '/' separator) and will have the case of the
590n/a# first path given in the sequence. Additionally, any trailing separator is
591n/a# stripped from the returned path.
592n/a
593n/adef commonpath(paths):
594n/a """Given a sequence of path names, returns the longest common sub-path."""
595n/a
596n/a if not paths:
597n/a raise ValueError('commonpath() arg is an empty sequence')
598n/a
599n/a paths = tuple(map(os.fspath, paths))
600n/a if isinstance(paths[0], bytes):
601n/a sep = b'\\'
602n/a altsep = b'/'
603n/a curdir = b'.'
604n/a else:
605n/a sep = '\\'
606n/a altsep = '/'
607n/a curdir = '.'
608n/a
609n/a try:
610n/a drivesplits = [splitdrive(p.replace(altsep, sep).lower()) for p in paths]
611n/a split_paths = [p.split(sep) for d, p in drivesplits]
612n/a
613n/a try:
614n/a isabs, = set(p[:1] == sep for d, p in drivesplits)
615n/a except ValueError:
616n/a raise ValueError("Can't mix absolute and relative paths") from None
617n/a
618n/a # Check that all drive letters or UNC paths match. The check is made only
619n/a # now otherwise type errors for mixing strings and bytes would not be
620n/a # caught.
621n/a if len(set(d for d, p in drivesplits)) != 1:
622n/a raise ValueError("Paths don't have the same drive")
623n/a
624n/a drive, path = splitdrive(paths[0].replace(altsep, sep))
625n/a common = path.split(sep)
626n/a common = [c for c in common if c and c != curdir]
627n/a
628n/a split_paths = [[c for c in s if c and c != curdir] for s in split_paths]
629n/a s1 = min(split_paths)
630n/a s2 = max(split_paths)
631n/a for i, c in enumerate(s1):
632n/a if c != s2[i]:
633n/a common = common[:i]
634n/a break
635n/a else:
636n/a common = common[:len(s1)]
637n/a
638n/a prefix = drive + sep if isabs else drive
639n/a return prefix + sep.join(common)
640n/a except (TypeError, AttributeError):
641n/a genericpath._check_arg_types('commonpath', *paths)
642n/a raise
643n/a
644n/a
645n/a# determine if two files are in fact the same file
646n/atry:
647n/a # GetFinalPathNameByHandle is available starting with Windows 6.0.
648n/a # Windows XP and non-Windows OS'es will mock _getfinalpathname.
649n/a if sys.getwindowsversion()[:2] >= (6, 0):
650n/a from nt import _getfinalpathname
651n/a else:
652n/a raise ImportError
653n/aexcept (AttributeError, ImportError):
654n/a # On Windows XP and earlier, two files are the same if their absolute
655n/a # pathnames are the same.
656n/a # Non-Windows operating systems fake this method with an XP
657n/a # approximation.
658n/a def _getfinalpathname(f):
659n/a return normcase(abspath(f))
660n/a
661n/a
662n/atry:
663n/a # The genericpath.isdir implementation uses os.stat and checks the mode
664n/a # attribute to tell whether or not the path is a directory.
665n/a # This is overkill on Windows - just pass the path to GetFileAttributes
666n/a # and check the attribute from there.
667n/a from nt import _isdir as isdir
668n/aexcept ImportError:
669n/a # Use genericpath.isdir as imported above.
670n/a pass