megfile 4.2.5__py3-none-any.whl → 5.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
megfile/smart_path.py CHANGED
@@ -1,54 +1,156 @@
1
1
  import os
2
- from configparser import ConfigParser
2
+ from functools import cached_property
3
3
  from pathlib import PurePath
4
4
  from typing import Dict, Optional, Tuple, Union
5
5
 
6
+ from megfile.config import load_megfile_config
6
7
  from megfile.lib.compat import fspath
7
8
  from megfile.lib.url import get_url_scheme
9
+ from megfile.pathlike import URIPathParents
8
10
  from megfile.utils import cached_classproperty
9
11
 
12
+ from .config import CaseSensitiveConfigParser
10
13
  from .errors import ProtocolExistsError, ProtocolNotFoundError
11
14
  from .interfaces import BasePath, PathLike
12
15
 
13
- aliases_config = "~/.config/megfile/aliases.conf"
16
+ LEGACY_ALIASES_CONFIG = "~/.config/megfile/aliases.conf"
14
17
 
15
18
 
16
- def _bind_function(name):
17
- def smart_method(self, *args, **kwargs):
18
- return getattr(self.pathlike, name)(*args, **kwargs)
19
+ def _bind_function(name, after_callback=None, before_callback=None):
20
+ if before_callback is None and after_callback is None:
21
+
22
+ def smart_method(self, *args, **kwargs):
23
+ return getattr(self.pathlike, name)(*args, **kwargs)
24
+
25
+ else:
26
+
27
+ def smart_method(self, *args, **kwargs):
28
+ if before_callback is not None and len(args) > 0:
29
+ first_arg = before_callback(self, args[0])
30
+ args = (first_arg, *args[1:])
31
+ result = getattr(self.pathlike, name)(*args, **kwargs)
32
+ if after_callback is not None:
33
+ return after_callback(self, result)
34
+ return result
19
35
 
20
36
  smart_method.__name__ = name
37
+ smart_method.__doc__ = f"Dynamically bound method for {name}"
21
38
 
22
39
  return smart_method
23
40
 
24
41
 
25
- def _bind_property(name):
26
- @property
27
- def smart_property(self):
28
- return getattr(self.pathlike, name)
42
+ def _bind_property(name, callback=None):
43
+ if callback is None:
44
+
45
+ @property
46
+ def smart_property(self):
47
+ return getattr(self.pathlike, name)
48
+
49
+ else:
50
+
51
+ @property
52
+ def smart_property(self):
53
+ return callback(self, getattr(self.pathlike, name))
29
54
 
30
55
  return smart_property
31
56
 
32
57
 
33
- def _load_aliases_config(config_path) -> Dict[str, Dict[str, str]]:
34
- if not os.path.exists(config_path):
35
- return {}
36
- parser = ConfigParser()
37
- parser.read(config_path)
58
+ def _load_aliases_config() -> Dict[str, Dict[str, str]]:
38
59
  configs = {}
39
- for section in parser.sections():
40
- configs[section] = dict(parser.items(section))
60
+ config_path = os.path.expanduser(LEGACY_ALIASES_CONFIG)
61
+ if os.path.isfile(config_path):
62
+ parser = CaseSensitiveConfigParser()
63
+ parser.read(config_path)
64
+ for section in parser.sections():
65
+ configs[section] = dict(parser.items(section))
66
+ for name, protocol_or_path in load_megfile_config("alias").items():
67
+ if "://" in protocol_or_path:
68
+ protocol, prefix = protocol_or_path.split("://", maxsplit=1)
69
+ configs[name] = {"protocol": protocol, "prefix": prefix}
70
+ else:
71
+ configs[name] = {"protocol": protocol_or_path}
41
72
  return configs
42
73
 
43
74
 
75
+ def _to_aliased_path(pathlike, other_path: str) -> str:
76
+ """Convert path string to aliased path string"""
77
+ if pathlike.protocol == pathlike._unaliased_protocol:
78
+ return other_path
79
+ aliases: Dict[str, Dict[str, str]] = pathlike._aliases
80
+ unaliased_prefix = aliases[pathlike.protocol].get("prefix", "")
81
+ unaliased_prefix = "%s://%s" % (pathlike._unaliased_protocol, unaliased_prefix)
82
+ if not other_path.startswith(unaliased_prefix):
83
+ return other_path
84
+ path_without_protocol = other_path[len(unaliased_prefix) :]
85
+ return f"{pathlike.protocol}://{path_without_protocol}"
86
+
87
+
88
+ def _to_aliased_pathlike(pathlike, other_pathlike) -> BasePath:
89
+ """Convert pathlike object to aliased SmartPath object"""
90
+ other_path = str(other_pathlike)
91
+ if pathlike.protocol != pathlike._unaliased_protocol:
92
+ other_path = _to_aliased_path(pathlike, other_path)
93
+ return SmartPath(other_path)
94
+
95
+
96
+ def _to_aliased_path_list(pathlike, other_paths):
97
+ """Convert list of path strings to aliased path strings"""
98
+ return [_to_aliased_path(pathlike, s) for s in other_paths]
99
+
100
+
101
+ def _to_aliased_pathlike_list(pathlike, other_pathlikes):
102
+ """Convert list of pathlike objects to aliased SmartPath objects"""
103
+ return [_to_aliased_pathlike(pathlike, p) for p in other_pathlikes]
104
+
105
+
106
+ def _to_aliased_path_iterator(pathlike, other_paths):
107
+ """Convert iterator of path strings to aliased path strings"""
108
+ for s in other_paths:
109
+ yield _to_aliased_path(pathlike, s)
110
+
111
+
112
+ def _to_aliased_pathlike_iterator(pathlike, other_pathlikes):
113
+ """Convert iterator of pathlike objects to aliased SmartPath objects"""
114
+ for p in other_pathlikes:
115
+ yield _to_aliased_pathlike(pathlike, p)
116
+
117
+
118
+ def _to_aliased_file_entry_iterator(pathlike, file_entries):
119
+ """Convert iterator of FileEntry objects with aliased paths"""
120
+ for entry in file_entries:
121
+ yield entry._replace(path=_to_aliased_path(pathlike, entry.path))
122
+
123
+
124
+ def _to_aliased_walk_iterator(pathlike, walk_iterator):
125
+ """Convert walk iterator with aliased paths"""
126
+ for dirpath, dirnames, filenames in walk_iterator:
127
+ aliased_dirpath = _to_aliased_path(pathlike, dirpath)
128
+ yield (aliased_dirpath, dirnames, filenames)
129
+
130
+
131
+ def _to_unaliased_path(pathlike, path):
132
+ """Convert path string to unaliased path string"""
133
+ aliases: Dict[str, Dict[str, str]] = pathlike._aliases
134
+ protocol, path_without_protocol = pathlike._split_protocol(path)
135
+ if protocol in aliases:
136
+ prefix = aliases[protocol].get("prefix", "")
137
+ protocol = aliases[protocol]["protocol"]
138
+ return "%s://%s%s" % (protocol, prefix, path_without_protocol)
139
+ return path
140
+
141
+
44
142
  class SmartPath(BasePath):
45
143
  _registered_protocols = dict()
46
144
 
47
145
  def __init__(self, path: Union[PathLike, int], *other_paths: PathLike):
48
146
  self.path = str(path) if not isinstance(path, int) else path
147
+ self.protocol = self._extract_protocol(path)
148
+ self._unaliased_path = _to_unaliased_path(self, path)
149
+ self._unaliased_protocol = self._extract_protocol(self._unaliased_path)
150
+
49
151
  pathlike = path
50
152
  if not isinstance(pathlike, BasePath):
51
- pathlike = self._create_pathlike(path)
153
+ pathlike = self._create_pathlike(self._unaliased_path)
52
154
  if len(other_paths) > 0:
53
155
  pathlike = pathlike.joinpath(*other_paths)
54
156
  self.path = str(pathlike)
@@ -56,45 +158,39 @@ class SmartPath(BasePath):
56
158
 
57
159
  @cached_classproperty
58
160
  def _aliases(cls) -> Dict[str, Dict[str, str]]:
59
- config_path = os.path.expanduser(aliases_config)
60
- return _load_aliases_config(config_path)
161
+ return _load_aliases_config()
61
162
 
62
163
  @classmethod
63
- def _extract_protocol(
64
- cls, path: Union[PathLike, int]
65
- ) -> Tuple[str, Union[str, int]]:
164
+ def _split_protocol(cls, path: Union[PathLike, int]) -> Tuple[str, Union[str, int]]:
66
165
  if isinstance(path, int):
67
- protocol = "file"
68
- path_without_protocol = path
166
+ return "file", path
69
167
  elif isinstance(path, str):
70
168
  protocol = get_url_scheme(path)
71
- if protocol == "":
169
+ if not protocol:
72
170
  protocol = "file"
73
171
  path_without_protocol = path
74
172
  else:
75
173
  path_without_protocol = path[len(protocol) + 3 :]
174
+ return protocol, path_without_protocol
76
175
  elif isinstance(path, (BasePath, SmartPath)):
77
- return str(path.protocol), str(path)
176
+ return str(path.protocol), path.path_without_protocol
78
177
  elif isinstance(path, (PurePath, BasePath)):
79
- return SmartPath._extract_protocol(fspath(path))
80
- else:
81
- raise ProtocolNotFoundError("protocol not found: %r" % path)
82
- aliases: Dict[str, Dict[str, str]] = cls._aliases # pyre-ignore[9]
83
- if protocol in aliases:
84
- prefix = aliases[protocol].get("prefix", "")
85
- protocol = aliases[protocol]["protocol"]
86
- path = "%s://%s%s" % (protocol, prefix, path_without_protocol)
87
- return protocol, path
178
+ return SmartPath._split_protocol(fspath(path))
179
+ raise ProtocolNotFoundError("protocol not found: %r" % path)
180
+
181
+ @classmethod
182
+ def _extract_protocol(cls, path: Union[PathLike, int]) -> str:
183
+ return cls._split_protocol(path)[0]
88
184
 
89
185
  @classmethod
90
186
  def _create_pathlike(cls, path: Union[PathLike, int]) -> BasePath:
91
- protocol, un_aliased_path = cls._extract_protocol(path)
187
+ protocol = cls._extract_protocol(path)
92
188
  if protocol.startswith("s3+"):
93
189
  protocol = "s3"
94
190
  if protocol not in cls._registered_protocols:
95
191
  raise ProtocolNotFoundError("protocol %r not found: %r" % (protocol, path))
96
192
  path_class = cls._registered_protocols[protocol]
97
- return path_class(un_aliased_path)
193
+ return path_class(path)
98
194
 
99
195
  @classmethod
100
196
  def register(cls, path_class, override_ok: bool = False):
@@ -104,81 +200,87 @@ class SmartPath(BasePath):
104
200
  cls._registered_protocols[protocol] = path_class
105
201
  return path_class
106
202
 
107
- symlink = _bind_function("symlink")
108
- symlink_to = _bind_function("symlink_to")
109
- hardlink_to = _bind_function("hardlink_to")
110
- readlink = _bind_function("readlink")
203
+ @classmethod
204
+ def from_uri(cls, path: PathLike):
205
+ return cls(path)
206
+
207
+ def relpath(self, start: Optional[str] = None) -> str:
208
+ """Return the relative path of given path
209
+
210
+ :param start: Given start directory
211
+ :returns: Relative path from start
212
+ """
213
+ if start is not None:
214
+ start = _to_unaliased_path(self, start)
215
+ return self.pathlike.relpath(start=start)
216
+
217
+ @cached_property
218
+ def parts(self) -> Tuple[str, ...]:
219
+ """A tuple giving access to the path’s various components"""
220
+ parts = self.pathlike.parts
221
+ parts = (_to_aliased_path(self, parts[0]), *parts[1:])
222
+ return parts
223
+
224
+ @cached_property
225
+ def parents(self) -> "URIPathParents":
226
+ """
227
+ An immutable sequence providing access to the logical ancestors of the path
228
+ """
229
+ return URIPathParents(self)
230
+
231
+ symlink = _bind_function("symlink", before_callback=_to_unaliased_path)
232
+ symlink_to = _bind_function("symlink_to", before_callback=_to_unaliased_path)
233
+ hardlink_to = _bind_function("hardlink_to", before_callback=_to_unaliased_path)
234
+ readlink = _bind_function("readlink", _to_aliased_pathlike)
111
235
  is_dir = _bind_function("is_dir")
112
236
  is_file = _bind_function("is_file")
113
237
  is_symlink = _bind_function("is_symlink")
114
238
  access = _bind_function("access")
115
239
  exists = _bind_function("exists")
116
- listdir = _bind_function("listdir")
117
- scandir = _bind_function("scandir")
240
+ listdir = _bind_function("listdir", _to_aliased_path_list)
241
+ scandir = _bind_function("scandir", _to_aliased_file_entry_iterator)
118
242
  getsize = _bind_function("getsize")
119
243
  getmtime = _bind_function("getmtime")
120
244
  stat = _bind_function("stat")
121
245
  lstat = _bind_function("lstat")
122
246
  remove = _bind_function("remove")
123
- rename = _bind_function("rename")
124
- replace = _bind_function("replace")
247
+ rename = _bind_function("rename", _to_aliased_pathlike, _to_unaliased_path)
248
+ replace = _bind_function("replace", _to_aliased_pathlike, _to_unaliased_path)
125
249
  unlink = _bind_function("unlink")
126
250
  mkdir = _bind_function("mkdir")
127
251
  open = _bind_function("open")
128
252
  touch = _bind_function("touch")
129
- walk = _bind_function("walk")
130
- scan = _bind_function("scan")
131
- scan_stat = _bind_function("scan_stat")
132
- glob = _bind_function("glob")
133
- iglob = _bind_function("iglob")
134
- glob_stat = _bind_function("glob_stat")
253
+ walk = _bind_function("walk", _to_aliased_walk_iterator)
254
+ scan = _bind_function("scan", _to_aliased_path_iterator)
255
+ scan_stat = _bind_function("scan_stat", _to_aliased_file_entry_iterator)
256
+ glob = _bind_function("glob", _to_aliased_pathlike_list)
257
+ iglob = _bind_function("iglob", _to_aliased_pathlike_iterator)
258
+ glob_stat = _bind_function("glob_stat", _to_aliased_file_entry_iterator)
135
259
  load = _bind_function("load")
136
260
  save = _bind_function("save")
137
- joinpath = _bind_function("joinpath")
138
- abspath = _bind_function("abspath")
139
- realpath = _bind_function("realpath")
261
+ joinpath = _bind_function("joinpath", _to_aliased_pathlike)
262
+ abspath = _bind_function("abspath", _to_aliased_path)
263
+ realpath = _bind_function("realpath", _to_aliased_path)
140
264
  is_absolute = _bind_function("is_absolute")
141
265
  is_mount = _bind_function("is_mount")
142
266
  md5 = _bind_function("md5")
143
267
 
144
- @property
145
- def protocol(self) -> str:
146
- return self.pathlike.protocol
147
-
148
- @classmethod
149
- def from_uri(cls, path: PathLike):
150
- return cls(path)
151
-
152
- def relpath(self, start: Optional[str] = None) -> str:
153
- """Return the relative path of given path
154
-
155
- :param start: Given start directory
156
- :returns: Relative path from start
157
- """
158
- if start is not None:
159
- _, start = SmartPath._extract_protocol(fspath(start)) # pyre-ignore[9]
160
- return self.pathlike.relpath(start=start)
161
-
162
- as_uri = _bind_function("as_uri")
163
- as_posix = _bind_function("as_posix")
164
- __lt__ = _bind_function("__lt__")
165
- __le__ = _bind_function("__le__")
166
- __gt__ = _bind_function("__gt__")
167
- __ge__ = _bind_function("__ge__")
168
- __fspath__ = _bind_function("__fspath__")
169
- __truediv__ = _bind_function("__truediv__")
268
+ as_uri = _bind_function("as_uri", _to_aliased_path)
269
+ as_posix = _bind_function("as_posix", _to_aliased_path)
270
+ __fspath__ = _bind_function("__fspath__", _to_aliased_path)
271
+ __truediv__ = _bind_function("__truediv__", _to_aliased_pathlike)
170
272
 
171
273
  is_reserved = _bind_function("is_reserved")
172
- match = _bind_function("match")
173
- relative_to = _bind_function("relative_to")
174
- with_name = _bind_function("with_name")
175
- with_suffix = _bind_function("with_suffix")
176
- with_stem = _bind_function("with_stem")
177
- iterdir = _bind_function("iterdir")
178
- cwd = _bind_function("cwd")
274
+ match = _bind_function("match", before_callback=_to_unaliased_path)
275
+ relative_to = _bind_function("relative_to", before_callback=_to_unaliased_path)
276
+ with_name = _bind_function("with_name", _to_aliased_pathlike)
277
+ with_suffix = _bind_function("with_suffix", _to_aliased_pathlike)
278
+ with_stem = _bind_function("with_stem", _to_aliased_pathlike)
279
+ iterdir = _bind_function("iterdir", _to_aliased_pathlike_iterator)
280
+ cwd = _bind_function("cwd", _to_aliased_pathlike)
179
281
  home = _bind_function("home")
180
282
  expanduser = _bind_function("expanduser")
181
- resolve = _bind_function("resolve")
283
+ resolve = _bind_function("resolve", _to_aliased_pathlike)
182
284
  chmod = _bind_function("chmod")
183
285
  lchmod = _bind_function("lchmod")
184
286
  group = _bind_function("group")
@@ -187,23 +289,23 @@ class SmartPath(BasePath):
187
289
  is_block_device = _bind_function("is_block_device")
188
290
  is_char_device = _bind_function("is_char_device")
189
291
  owner = _bind_function("owner")
190
- absolute = _bind_function("absolute")
292
+ absolute = _bind_function("absolute", _to_aliased_pathlike)
191
293
  rmdir = _bind_function("rmdir")
192
- is_relative_to = _bind_function("is_relative_to")
294
+ is_relative_to = _bind_function(
295
+ "is_relative_to", before_callback=_to_unaliased_path
296
+ )
193
297
  read_bytes = _bind_function("read_bytes")
194
298
  read_text = _bind_function("read_text")
195
- rglob = _bind_function("rglob")
299
+ rglob = _bind_function("rglob", _to_aliased_pathlike_list)
196
300
  samefile = _bind_function("samefile")
197
301
  write_bytes = _bind_function("write_bytes")
198
302
  write_text = _bind_function("write_text")
199
303
  utime = _bind_function("utime")
200
304
 
201
305
  drive = _bind_property("drive")
202
- root = _bind_property("root")
203
- anchor = _bind_property("anchor")
204
- parts = _bind_property("parts")
205
- parents = _bind_property("parents")
206
- parent = _bind_property("parent")
306
+ root = _bind_property("root", _to_aliased_path)
307
+ anchor = _bind_property("anchor", _to_aliased_path)
308
+ parent = _bind_property("parent", _to_aliased_pathlike)
207
309
  name = _bind_property("name")
208
310
  suffix = _bind_property("suffix")
209
311
  suffixes = _bind_property("suffixes")
megfile/version.py CHANGED
@@ -1 +1 @@
1
- VERSION = "4.2.5"
1
+ VERSION = "5.0.1"