source: ftputil/path.py @ 1721:3557f65ded13

Last change on this file since 1721:3557f65ded13 was 1721:3557f65ded13, checked in by Stefan Schwarzer <sschwarzer@…>, 3 years ago
Remove `compat.py` This module was for Python 2/3 compatibility, similar to the `six` package. Since the next version of ftputil will only support Python 3, hardcode the types from `compat.py` in the code that used to use the `compat` module. There may still be redundant code that isn't needed when running the tests under Python 3.
File size: 8.0 KB
Line 
1# Copyright (C) 2003-2018, Stefan Schwarzer <sschwarzer@sschwarzer.net>
2# and ftputil contributors (see `doc/contributors.txt`)
3# See the file LICENSE for licensing terms.
4
5"""
6ftputil.path - simulate `os.path` for FTP servers
7"""
8
9import posixpath
10import stat
11
12import ftputil.error
13import ftputil.tool
14
15
16# The `_Path` class shouldn't be used directly by clients of the
17# ftputil library.
18__all__ = []
19
20
21class _Path:
22    """
23    Support class resembling `os.path`, accessible from the `FTPHost`
24    object, e. g. as `FTPHost().path.abspath(path)`.
25
26    Hint: substitute `os` with the `FTPHost` object.
27    """
28
29    # `_Path` needs to provide all methods of `os.path`.
30    # pylint: disable=too-many-instance-attributes
31
32    def __init__(self, host):
33        self._host = host
34        # Delegate these to the `posixpath` module.
35        # pylint: disable=invalid-name
36        pp = posixpath
37        self.dirname      = pp.dirname
38        self.basename     = pp.basename
39        self.isabs        = pp.isabs
40        self.commonprefix = pp.commonprefix
41        self.split        = pp.split
42        self.splitdrive   = pp.splitdrive
43        self.splitext     = pp.splitext
44        self.normcase     = pp.normcase
45        self.normpath     = pp.normpath
46
47    def abspath(self, path):
48        """Return an absolute path."""
49        original_path = path
50        path = ftputil.tool.as_unicode(path)
51        if not self.isabs(path):
52            path = self.join(self._host.getcwd(), path)
53        return ftputil.tool.same_string_type_as(original_path,
54                                                self.normpath(path))
55
56    def exists(self, path):
57        """Return true if the path exists."""
58        try:
59            lstat_result = self._host.lstat(
60                             path, _exception_for_missing_path=False)
61            return lstat_result is not None
62        except ftputil.error.RootDirError:
63            return True
64
65    def getmtime(self, path):
66        """
67        Return the timestamp for the last modification for `path`
68        as a float.
69
70        This will raise `PermanentError` if the path doesn't exist,
71        but maybe other exceptions depending on the state of the
72        server (e. g. timeout).
73        """
74        return self._host.stat(path).st_mtime
75
76    def getsize(self, path):
77        """
78        Return the size of the `path` item as an integer.
79
80        This will raise `PermanentError` if the path doesn't exist,
81        but maybe raise other exceptions depending on the state of the
82        server (e. g. timeout).
83        """
84        return self._host.stat(path).st_size
85
86    @staticmethod
87    def join(*paths):
88        """
89        Join the path component from `paths` and return the joined
90        path.
91
92        All of these paths must be either unicode strings or byte
93        strings. If not, `join` raises a `TypeError`.
94        """
95        # These checks are implicitly done by Python 3, but not by
96        # Python 2.
97        all_paths_are_unicode = all((isinstance(path, str)
98                                     for path in paths))
99        all_paths_are_bytes = all((isinstance(path, bytes)
100                                   for path in paths))
101        if all_paths_are_unicode or all_paths_are_bytes:
102            return posixpath.join(*paths)
103        else:
104            # Python 3 raises this exception for mixed strings
105            # in `os.path.join`, so also use this exception.
106            raise TypeError(
107                    "can't mix unicode strings and bytes in path components")
108
109    # Check whether a path is a regular file/dir/link. For the first
110    # two cases follow links (like in `os.path`).
111    #
112    # Implementation note: The previous implementations simply called
113    # `stat` or `lstat` and returned `False` if they ended with
114    # raising a `PermanentError`. That exception usually used to
115    # signal a missing path. This approach has the problem, however,
116    # that exceptions caused by code earlier in `lstat` are obscured
117    # by the exception handling in `isfile`, `isdir` and `islink`.
118
119    def _is_file_system_entity(self, path, dir_or_file):
120        """
121        Return `True` if `path` represents the file system entity
122        described by `dir_or_file` ("dir" or "file").
123
124        Return `False` if `path` isn't a directory or file,
125        respectively or if `path` leads to an infinite chain of links.
126        """
127        assert dir_or_file in ["dir", "file"]
128        # Consider differences between directories and files.
129        if dir_or_file == "dir":
130            should_look_for_dir = True
131            stat_function = stat.S_ISDIR
132        else:
133            should_look_for_dir = False
134            stat_function = stat.S_ISREG
135        #
136        path = ftputil.tool.as_unicode(path)
137        #  Workaround if we can't go up from the current directory.
138        #  The result from `getcwd` should already be normalized.
139        if self.normpath(path) == self._host.getcwd():
140            return should_look_for_dir
141        try:
142            stat_result = self._host.stat(
143                            path, _exception_for_missing_path=False)
144        except ftputil.error.RecursiveLinksError:
145            return False
146        except ftputil.error.RootDirError:
147            return should_look_for_dir
148        else:
149            if stat_result is None:
150                # Non-existent path
151                return False
152            else:
153                return stat_function(stat_result.st_mode)
154
155    def isdir(self, path):
156        """
157        Return true if the `path` exists and corresponds to a
158        directory (no link).
159
160        A non-existing path does _not_ cause a `PermanentError`.
161        """
162        return self._is_file_system_entity(path, "dir")
163
164    def isfile(self, path):
165        """
166        Return true if the `path` exists and corresponds to a regular
167        file (no link).
168
169        A non-existing path does _not_ cause a `PermanentError`.
170        """
171        return self._is_file_system_entity(path, "file")
172
173    def islink(self, path):
174        """
175        Return true if the `path` exists and is a link.
176
177        A non-existing path does _not_ cause a `PermanentError`.
178        """
179        path = ftputil.tool.as_unicode(path)
180        try:
181            lstat_result = self._host.lstat(
182                             path, _exception_for_missing_path=False)
183        except ftputil.error.RootDirError:
184            return False
185        else:
186            if lstat_result is None:
187                # Non-existent path
188                return False
189            else:
190                return stat.S_ISLNK(lstat_result.st_mode)
191
192    def walk(self, top, func, arg):
193        """
194        Directory tree walk with callback function.
195
196        For each directory in the directory tree rooted at top
197        (including top itself, but excluding "." and ".."), call
198        func(arg, dirname, fnames). dirname is the name of the
199        directory, and fnames a list of the names of the files and
200        subdirectories in dirname (excluding "." and "..").  func may
201        modify the fnames list in-place (e.g. via del or slice
202        assignment), and walk will only recurse into the
203        subdirectories whose names remain in fnames; this can be used
204        to implement a filter, or to impose a specific order of
205        visiting.  No semantics are defined for, or required of, arg,
206        beyond that arg is always passed to func.  It can be used,
207        e.g., to pass a filename pattern, or a mutable object designed
208        to accumulate statistics.  Passing None for arg is common.
209        """
210        top = ftputil.tool.as_unicode(top)
211        # This code (and the above documentation) is taken from
212        # `posixpath.py`, with slight modifications.
213        try:
214            names = self._host.listdir(top)
215        except OSError:
216            return
217        func(arg, top, names)
218        for name in names:
219            name = self.join(top, name)
220            try:
221                stat_result = self._host.lstat(name)
222            except OSError:
223                continue
224            if stat.S_ISDIR(stat_result[stat.ST_MODE]):
225                self.walk(name, func, arg)
Note: See TracBrowser for help on using the repository browser.