| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -266,6 +266,15 @@ the :mod:`glob` module.) | |||
| 266 | 266 | Accepts a :term:`path-like object`. | |
| 267 | 267 | ||
| 268 | 268 | ||
| 269 | + .. function:: isjunction(path) | ||
| 270 | + | ||
| 271 | + Return ``True`` if *path* refers to an :func:`existing <lexists>` directory | ||
| 272 | + entry that is a junction. Always return ``False`` if junctions are not | ||
| 273 | + supported on the current platform. | ||
| 274 | + | ||
| 275 | + .. versionadded:: 3.12 | ||
| 276 | + | ||
| 277 | + | ||
| 269 | 278 | .. function:: islink(path) | |
| 270 | 279 | ||
| 271 | 280 | Return ``True`` if *path* refers to an :func:`existing <exists>` directory | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2738,6 +2738,17 @@ features: | |||
| 2738 | 2738 | This method can raise :exc:`OSError`, such as :exc:`PermissionError`, | |
| 2739 | 2739 | but :exc:`FileNotFoundError` is caught and not raised. | |
| 2740 | 2740 | ||
| 2741 | + .. method:: is_junction() | ||
| 2742 | + | ||
| 2743 | + Return ``True`` if this entry is a junction (even if broken); | ||
| 2744 | + return ``False`` if the entry points to a regular directory, any kind | ||
| 2745 | + of file, a symlink, or if it doesn't exist anymore. | ||
| 2746 | + | ||
| 2747 | + The result is cached on the ``os.DirEntry`` object. Call | ||
| 2748 | + :func:`os.path.isjunction` to fetch up-to-date information. | ||
| 2749 | + | ||
| 2750 | + .. versionadded:: 3.12 | ||
| 2751 | + | ||
| 2741 | 2752 | .. method:: stat(*, follow_symlinks=True) | |
| 2742 | 2753 | ||
| 2743 | 2754 | Return a :class:`stat_result` object for this entry. This method | |
@@ -2760,8 +2771,8 @@ features: | |||
| 2760 | 2771 | Note that there is a nice correspondence between several attributes | |
| 2761 | 2772 | and methods of ``os.DirEntry`` and of :class:`pathlib.Path`. In | |
| 2762 | 2773 | particular, the ``name`` attribute has the same | |
| 2763 | - meaning, as do the ``is_dir()``, ``is_file()``, ``is_symlink()`` | ||
| 2764 | - and ``stat()`` methods. | ||
| 2774 | + meaning, as do the ``is_dir()``, ``is_file()``, ``is_symlink()``, | ||
| 2775 | + ``is_junction()``, and ``stat()`` methods. | ||
| 2765 | 2776 | ||
| 2766 | 2777 | .. versionadded:: 3.5 | |
| 2767 | 2778 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -891,6 +891,14 @@ call fails (for example because the path doesn't exist). | |||
| 891 | 891 | other errors (such as permission errors) are propagated. | |
| 892 | 892 | ||
| 893 | 893 | ||
| 894 | + .. method:: Path.is_junction() | ||
| 895 | + | ||
| 896 | + Return ``True`` if the path points to a junction, and ``False`` for any other | ||
| 897 | + type of file. Currently only Windows supports junctions. | ||
| 898 | + | ||
| 899 | + .. versionadded:: 3.12 | ||
| 900 | + | ||
| 901 | + | ||
| 894 | 902 | .. method:: Path.is_mount() | |
| 895 | 903 | ||
| 896 | 904 | Return ``True`` if the path is a :dfn:`mount point`: a point in a | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -234,6 +234,10 @@ pathlib | |||
| 234 | 234 | more consistent with :func:`os.path.relpath`. | |
| 235 | 235 | (Contributed by Domenico Ragusa in :issue:`40358`.) | |
| 236 | 236 | ||
| 237 | + * Add :meth:`pathlib.Path.is_junction` as a proxy to :func:`os.path.isjunction`. | ||
| 238 | + (Contributed by Charles Machalow in :gh:`99547`.) | ||
| 239 | + | ||
| 240 | + | ||
| 237 | 241 | dis | |
| 238 | 242 | --- | |
| 239 | 243 | ||
@@ -252,6 +256,14 @@ os | |||
| 252 | 256 | for a process with :func:`os.pidfd_open` in non-blocking mode. | |
| 253 | 257 | (Contributed by Kumar Aditya in :gh:`93312`.) | |
| 254 | 258 | ||
| 259 | + * Add :func:`os.path.isjunction` to check if a given path is a junction. | ||
| 260 | + (Contributed by Charles Machalow in :gh:`99547`.) | ||
| 261 | + | ||
| 262 | + * :class:`os.DirEntry` now includes an :meth:`os.DirEntry.is_junction` | ||
| 263 | + method to check if the entry is a junction. | ||
| 264 | + (Contributed by Charles Machalow in :gh:`99547`.) | ||
| 265 | + | ||
| 266 | + | ||
| 255 | 267 | shutil | |
| 256 | 268 | ------ | |
| 257 | 269 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -30,7 +30,7 @@ | |||
| 30 | 30 | "ismount", "expanduser","expandvars","normpath","abspath", | |
| 31 | 31 | "curdir","pardir","sep","pathsep","defpath","altsep", | |
| 32 | 32 | "extsep","devnull","realpath","supports_unicode_filenames","relpath", | |
| 33 | - "samefile", "sameopenfile", "samestat", "commonpath"] | ||
| 33 | + "samefile", "sameopenfile", "samestat", "commonpath", "isjunction"] | ||
| 34 | 34 | ||
| 35 | 35 | def _get_bothseps(path): | |
| 36 | 36 | if isinstance(path, bytes): | |
@@ -267,6 +267,24 @@ def islink(path): | |||
| 267 | 267 | return False | |
| 268 | 268 | return stat.S_ISLNK(st.st_mode) | |
| 269 | 269 | ||
| 270 | + | ||
| 271 | + # Is a path a junction? | ||
| 272 | + | ||
| 273 | + if hasattr(os.stat_result, 'st_reparse_tag'): | ||
| 274 | + def isjunction(path): | ||
| 275 | + """Test whether a path is a junction""" | ||
| 276 | + try: | ||
| 277 | + st = os.lstat(path) | ||
| 278 | + except (OSError, ValueError, AttributeError): | ||
| 279 | + return False | ||
| 280 | + return bool(st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT) | ||
| 281 | + else: | ||
| 282 | + def isjunction(path): | ||
| 283 | + """Test whether a path is a junction""" | ||
| 284 | + os.fspath(path) | ||
| 285 | + return False | ||
| 286 | + | ||
| 287 | + | ||
| 270 | 288 | # Being true for dangling symbolic links is also useful. | |
| 271 | 289 | ||
| 272 | 290 | def lexists(path): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1223,6 +1223,12 @@ def is_symlink(self): | |||
| 1223 | 1223 | # Non-encodable path | |
| 1224 | 1224 | return False | |
| 1225 | 1225 | ||
| 1226 | + def is_junction(self): | ||
| 1227 | + """ | ||
| 1228 | + Whether this path is a junction. | ||
| 1229 | + """ | ||
| 1230 | + return self._flavour.pathmod.isjunction(self) | ||
| 1231 | + | ||
| 1226 | 1232 | def is_block_device(self): | |
| 1227 | 1233 | """ | |
| 1228 | 1234 | Whether this path is a block device. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -35,7 +35,7 @@ | |||
| 35 | 35 | "samefile","sameopenfile","samestat", | |
| 36 | 36 | "curdir","pardir","sep","pathsep","defpath","altsep","extsep", | |
| 37 | 37 | "devnull","realpath","supports_unicode_filenames","relpath", | |
| 38 | - "commonpath"] | ||
| 38 | + "commonpath", "isjunction"] | ||
| 39 | 39 | ||
| 40 | 40 | ||
| 41 | 41 | def _get_sep(path): | |
@@ -169,6 +169,16 @@ def islink(path): | |||
| 169 | 169 | return False | |
| 170 | 170 | return stat.S_ISLNK(st.st_mode) | |
| 171 | 171 | ||
| 172 | + | ||
| 173 | + # Is a path a junction? | ||
| 174 | + | ||
| 175 | + def isjunction(path): | ||
| 176 | + """Test whether a path is a junction | ||
| 177 | + Junctions are not a part of posix semantics""" | ||
| 178 | + os.fspath(path) | ||
| 179 | + return False | ||
| 180 | + | ||
| 181 | + | ||
| 172 | 182 | # Being true for dangling symbolic links is also useful. | |
| 173 | 183 | ||
| 174 | 184 | def lexists(path): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -565,18 +565,6 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, | |||
| 565 | 565 | dirs_exist_ok=dirs_exist_ok) | |
| 566 | 566 | ||
| 567 | 567 | if hasattr(os.stat_result, 'st_file_attributes'): | |
| 568 | - # Special handling for directory junctions to make them behave like | ||
| 569 | - # symlinks for shutil.rmtree, since in general they do not appear as | ||
| 570 | - # regular links. | ||
| 571 | - def _rmtree_isdir(entry): | ||
| 572 | - try: | ||
| 573 | - st = entry.stat(follow_symlinks=False) | ||
| 574 | - return (stat.S_ISDIR(st.st_mode) and not | ||
| 575 | - (st.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT | ||
| 576 | - and st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT)) | ||
| 577 | - except OSError: | ||
| 578 | - return False | ||
| 579 | - | ||
| 580 | 568 | def _rmtree_islink(path): | |
| 581 | 569 | try: | |
| 582 | 570 | st = os.lstat(path) | |
@@ -586,12 +574,6 @@ def _rmtree_islink(path): | |||
| 586 | 574 | except OSError: | |
| 587 | 575 | return False | |
| 588 | 576 | else: | |
| 589 | - def _rmtree_isdir(entry): | ||
| 590 | - try: | ||
| 591 | - return entry.is_dir(follow_symlinks=False) | ||
| 592 | - except OSError: | ||
| 593 | - return False | ||
| 594 | - | ||
| 595 | 577 | def _rmtree_islink(path): | |
| 596 | 578 | return os.path.islink(path) | |
| 597 | 579 | ||
@@ -605,7 +587,12 @@ def _rmtree_unsafe(path, onerror): | |||
| 605 | 587 | entries = [] | |
| 606 | 588 | for entry in entries: | |
| 607 | 589 | fullname = entry.path | |
| 608 | - if _rmtree_isdir(entry): | ||
| 590 | + try: | ||
| 591 | + is_dir = entry.is_dir(follow_symlinks=False) | ||
| 592 | + except OSError: | ||
| 593 | + is_dir = False | ||
| 594 | + | ||
| 595 | + if is_dir and not entry.is_junction(): | ||
| 609 | 596 | try: | |
| 610 | 597 | if entry.is_symlink(): | |
| 611 | 598 | # This can only happen if someone replaces | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -856,6 +856,23 @@ def test_nt_helpers(self): | |||
| 856 | 856 | self.assertIsInstance(b_final_path, bytes) | |
| 857 | 857 | self.assertGreater(len(b_final_path), 0) | |
| 858 | 858 | ||
| 859 | + @unittest.skipIf(sys.platform != 'win32', "Can only test junctions with creation on win32.") | ||
| 860 | + def test_isjunction(self): | ||
| 861 | + with os_helper.temp_dir() as d: | ||
| 862 | + with os_helper.change_cwd(d): | ||
| 863 | + os.mkdir('tmpdir') | ||
| 864 | + | ||
| 865 | + import _winapi | ||
| 866 | + try: | ||
| 867 | + _winapi.CreateJunction('tmpdir', 'testjunc') | ||
| 868 | + except OSError: | ||
| 869 | + raise unittest.SkipTest('creating the test junction failed') | ||
| 870 | + | ||
| 871 | + self.assertTrue(ntpath.isjunction('testjunc')) | ||
| 872 | + self.assertFalse(ntpath.isjunction('tmpdir')) | ||
| 873 | + self.assertPathEqual(ntpath.realpath('testjunc'), ntpath.realpath('tmpdir')) | ||
| 874 | + | ||
| 875 | + | ||
| 859 | 876 | class NtCommonTest(test_genericpath.CommonTest, unittest.TestCase): | |
| 860 | 877 | pathmodule = ntpath | |
| 861 | 878 | attributes = ['relpath'] | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4158,6 +4158,8 @@ def check_entry(self, entry, name, is_dir, is_file, is_symlink): | |||
| 4158 | 4158 | self.assertEqual(entry.is_file(follow_symlinks=False), | |
| 4159 | 4159 | stat.S_ISREG(entry_lstat.st_mode)) | |
| 4160 | 4160 | ||
| 4161 | + self.assertEqual(entry.is_junction(), os.path.isjunction(entry.path)) | ||
| 4162 | + | ||
| 4161 | 4163 | self.assert_stat_equal(entry.stat(), | |
| 4162 | 4164 | entry_stat, | |
| 4163 | 4165 | os.name == 'nt' and not is_symlink) | |
@@ -4206,6 +4208,21 @@ def test_attributes(self): | |||
| 4206 | 4208 | entry = entries['symlink_file.txt'] | |
| 4207 | 4209 | self.check_entry(entry, 'symlink_file.txt', False, True, True) | |
| 4208 | 4210 | ||
| 4211 | + @unittest.skipIf(sys.platform != 'win32', "Can only test junctions with creation on win32.") | ||
| 4212 | + def test_attributes_junctions(self): | ||
| 4213 | + dirname = os.path.join(self.path, "tgtdir") | ||
| 4214 | + os.mkdir(dirname) | ||
| 4215 | + | ||
| 4216 | + import _winapi | ||
| 4217 | + try: | ||
| 4218 | + _winapi.CreateJunction(dirname, os.path.join(self.path, "srcjunc")) | ||
| 4219 | + except OSError: | ||
| 4220 | + raise unittest.SkipTest('creating the test junction failed') | ||
| 4221 | + | ||
| 4222 | + entries = self.get_entries(['srcjunc', 'tgtdir']) | ||
| 4223 | + self.assertEqual(entries['srcjunc'].is_junction(), True) | ||
| 4224 | + self.assertEqual(entries['tgtdir'].is_junction(), False) | ||
| 4225 | + | ||
| 4209 | 4226 | def get_entry(self, name): | |
| 4210 | 4227 | path = self.bytes_path if isinstance(name, bytes) else self.path | |
| 4211 | 4228 | entries = list(os.scandir(path)) | |
| Back | FazBrowse Home | New Git URL |
0 commit comments