rclone-api 1.5.40__py3-none-any.whl → 1.5.42__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.
rclone_api/config.py CHANGED
@@ -47,11 +47,22 @@ class Parsed:
47
47
  return parse_rclone_config(content)
48
48
 
49
49
 
50
- @dataclass
51
50
  class Config:
52
51
  """Rclone configuration dataclass."""
53
52
 
54
- text: str
53
+ # text: str
54
+ def __init__(self, text: str | dict) -> None:
55
+ self.text: str
56
+ if isinstance(text, dict):
57
+ self.text = _json_to_rclone_config_str_or_raise(text)
58
+ else:
59
+ self.text = text
60
+
61
+ try:
62
+ new_text = _json_to_rclone_config_str_or_raise(self.text)
63
+ self.text = new_text
64
+ except Exception:
65
+ pass
55
66
 
56
67
  @staticmethod
57
68
  def from_json(json_data: dict) -> "Config | Exception":
@@ -168,8 +179,13 @@ def parse_rclone_config(content: str) -> Parsed:
168
179
  # }
169
180
 
170
181
 
171
- def _json_to_rclone_config_str_or_raise(json_data: dict) -> str:
182
+ def _json_to_rclone_config_str_or_raise(json_data: dict | str) -> str:
172
183
  """Convert JSON data to rclone config."""
184
+ import json
185
+
186
+ if isinstance(json_data, str):
187
+ json_data = json.loads(json_data)
188
+ assert isinstance(json_data, dict)
173
189
  out = ""
174
190
  for key, value in json_data.items():
175
191
  out += f"[{key}]\n"
rclone_api/db/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
- from .db import DB
2
-
3
- __all__ = ["DB"]
1
+ from .db import DB
2
+
3
+ __all__ = ["DB"]
rclone_api/detail/walk.py CHANGED
@@ -1,116 +1,116 @@
1
- import random
2
- from queue import Queue
3
- from threading import Thread
4
- from typing import Generator
5
-
6
- from rclone_api import Dir
7
- from rclone_api.dir_listing import DirListing
8
- from rclone_api.remote import Remote
9
- from rclone_api.types import Order
10
-
11
- _MAX_OUT_QUEUE_SIZE = 50
12
-
13
-
14
- def walk_runner_breadth_first(
15
- dir: Dir,
16
- max_depth: int,
17
- out_queue: Queue[DirListing | None],
18
- order: Order = Order.NORMAL,
19
- ) -> None:
20
- queue: Queue[Dir] = Queue()
21
- queue.put(dir)
22
- try:
23
- while not queue.empty():
24
- current_dir = queue.get()
25
- dirlisting = current_dir.ls(max_depth=0, order=order)
26
- out_queue.put(dirlisting)
27
- dirs = dirlisting.dirs
28
-
29
- if max_depth != 0 and len(dirs) > 0:
30
- for child in dirs:
31
- queue.put(child)
32
- if max_depth < 0:
33
- continue
34
- if max_depth > 0:
35
- max_depth -= 1
36
- out_queue.put(None)
37
- except KeyboardInterrupt:
38
- import _thread
39
-
40
- out_queue.put(None)
41
-
42
- _thread.interrupt_main()
43
-
44
-
45
- def walk_runner_depth_first(
46
- dir: Dir,
47
- max_depth: int,
48
- out_queue: Queue[DirListing | None],
49
- order: Order = Order.NORMAL,
50
- ) -> None:
51
- try:
52
- stack = [(dir, max_depth)]
53
- while stack:
54
- current_dir, depth = stack.pop()
55
- dirlisting = current_dir.ls()
56
- if order == Order.REVERSE:
57
- dirlisting.dirs.reverse()
58
- if order == Order.RANDOM:
59
-
60
- random.shuffle(dirlisting.dirs)
61
- if depth != 0:
62
- for subdir in dirlisting.dirs: # Process deeper directories first
63
- # stack.append((child, depth - 1 if depth > 0 else depth))
64
- next_depth = depth - 1 if depth > 0 else depth
65
- walk_runner_depth_first(subdir, next_depth, out_queue, order=order)
66
- out_queue.put(dirlisting)
67
- out_queue.put(None)
68
- except KeyboardInterrupt:
69
- import _thread
70
-
71
- out_queue.put(None)
72
- _thread.interrupt_main()
73
-
74
-
75
- def walk(
76
- dir: Dir | Remote,
77
- breadth_first: bool,
78
- max_depth: int = -1,
79
- order: Order = Order.NORMAL,
80
- ) -> Generator[DirListing, None, None]:
81
- """Walk through the given directory recursively.
82
-
83
- Args:
84
- dir: Directory or Remote to walk through
85
- max_depth: Maximum depth to traverse (-1 for unlimited)
86
-
87
- Yields:
88
- DirListing: Directory listing for each directory encountered
89
- """
90
- try:
91
- # Convert Remote to Dir if needed
92
- if isinstance(dir, Remote):
93
- dir = Dir(dir)
94
- out_queue: Queue[DirListing | None] = Queue(maxsize=_MAX_OUT_QUEUE_SIZE)
95
-
96
- def _task() -> None:
97
- if breadth_first:
98
- walk_runner_breadth_first(dir, max_depth, out_queue, order)
99
- else:
100
- walk_runner_depth_first(dir, max_depth, out_queue, order)
101
-
102
- # Start worker thread
103
- worker = Thread(
104
- target=_task,
105
- daemon=True,
106
- )
107
- worker.start()
108
-
109
- while dirlisting := out_queue.get():
110
- if dirlisting is None:
111
- break
112
- yield dirlisting
113
-
114
- worker.join()
115
- except KeyboardInterrupt:
116
- pass
1
+ import random
2
+ from queue import Queue
3
+ from threading import Thread
4
+ from typing import Generator
5
+
6
+ from rclone_api import Dir
7
+ from rclone_api.dir_listing import DirListing
8
+ from rclone_api.remote import Remote
9
+ from rclone_api.types import Order
10
+
11
+ _MAX_OUT_QUEUE_SIZE = 50
12
+
13
+
14
+ def walk_runner_breadth_first(
15
+ dir: Dir,
16
+ max_depth: int,
17
+ out_queue: Queue[DirListing | None],
18
+ order: Order = Order.NORMAL,
19
+ ) -> None:
20
+ queue: Queue[Dir] = Queue()
21
+ queue.put(dir)
22
+ try:
23
+ while not queue.empty():
24
+ current_dir = queue.get()
25
+ dirlisting = current_dir.ls(max_depth=0, order=order)
26
+ out_queue.put(dirlisting)
27
+ dirs = dirlisting.dirs
28
+
29
+ if max_depth != 0 and len(dirs) > 0:
30
+ for child in dirs:
31
+ queue.put(child)
32
+ if max_depth < 0:
33
+ continue
34
+ if max_depth > 0:
35
+ max_depth -= 1
36
+ out_queue.put(None)
37
+ except KeyboardInterrupt:
38
+ import _thread
39
+
40
+ out_queue.put(None)
41
+
42
+ _thread.interrupt_main()
43
+
44
+
45
+ def walk_runner_depth_first(
46
+ dir: Dir,
47
+ max_depth: int,
48
+ out_queue: Queue[DirListing | None],
49
+ order: Order = Order.NORMAL,
50
+ ) -> None:
51
+ try:
52
+ stack = [(dir, max_depth)]
53
+ while stack:
54
+ current_dir, depth = stack.pop()
55
+ dirlisting = current_dir.ls()
56
+ if order == Order.REVERSE:
57
+ dirlisting.dirs.reverse()
58
+ if order == Order.RANDOM:
59
+
60
+ random.shuffle(dirlisting.dirs)
61
+ if depth != 0:
62
+ for subdir in dirlisting.dirs: # Process deeper directories first
63
+ # stack.append((child, depth - 1 if depth > 0 else depth))
64
+ next_depth = depth - 1 if depth > 0 else depth
65
+ walk_runner_depth_first(subdir, next_depth, out_queue, order=order)
66
+ out_queue.put(dirlisting)
67
+ out_queue.put(None)
68
+ except KeyboardInterrupt:
69
+ import _thread
70
+
71
+ out_queue.put(None)
72
+ _thread.interrupt_main()
73
+
74
+
75
+ def walk(
76
+ dir: Dir | Remote,
77
+ breadth_first: bool,
78
+ max_depth: int = -1,
79
+ order: Order = Order.NORMAL,
80
+ ) -> Generator[DirListing, None, None]:
81
+ """Walk through the given directory recursively.
82
+
83
+ Args:
84
+ dir: Directory or Remote to walk through
85
+ max_depth: Maximum depth to traverse (-1 for unlimited)
86
+
87
+ Yields:
88
+ DirListing: Directory listing for each directory encountered
89
+ """
90
+ try:
91
+ # Convert Remote to Dir if needed
92
+ if isinstance(dir, Remote):
93
+ dir = Dir(dir)
94
+ out_queue: Queue[DirListing | None] = Queue(maxsize=_MAX_OUT_QUEUE_SIZE)
95
+
96
+ def _task() -> None:
97
+ if breadth_first:
98
+ walk_runner_breadth_first(dir, max_depth, out_queue, order)
99
+ else:
100
+ walk_runner_depth_first(dir, max_depth, out_queue, order)
101
+
102
+ # Start worker thread
103
+ worker = Thread(
104
+ target=_task,
105
+ daemon=True,
106
+ )
107
+ worker.start()
108
+
109
+ while dirlisting := out_queue.get():
110
+ if dirlisting is None:
111
+ break
112
+ yield dirlisting
113
+
114
+ worker.join()
115
+ except KeyboardInterrupt:
116
+ pass
rclone_api/dir.py CHANGED
@@ -1,113 +1,113 @@
1
- import json
2
- from pathlib import Path
3
- from typing import Generator
4
-
5
- from rclone_api.dir_listing import DirListing
6
- from rclone_api.remote import Remote
7
- from rclone_api.rpath import RPath
8
- from rclone_api.types import ListingOption, Order
9
-
10
-
11
- class Dir:
12
- """Remote file dataclass."""
13
-
14
- @property
15
- def remote(self) -> Remote:
16
- return self.path.remote
17
-
18
- @property
19
- def name(self) -> str:
20
- return self.path.name
21
-
22
- def __init__(self, path: RPath | Remote) -> None:
23
- """Initialize Dir with either an RPath or Remote.
24
-
25
- Args:
26
- path: Either an RPath object or a Remote object
27
- """
28
- if isinstance(path, Remote):
29
- # Need to create an RPath for the Remote's root
30
- self.path = RPath(
31
- remote=path,
32
- path=str(path),
33
- name=str(path),
34
- size=0,
35
- mime_type="inode/directory",
36
- mod_time="",
37
- is_dir=True,
38
- )
39
- # Ensure the RPath has the same rclone instance as the Remote
40
- self.path.set_rclone(path.rclone)
41
- else:
42
- self.path = path
43
- # self.path.set_rclone(self.path.remote.rclone)
44
- assert self.path.rclone is not None
45
-
46
- def ls(
47
- self,
48
- max_depth: int | None = None,
49
- glob: str | None = None,
50
- order: Order = Order.NORMAL,
51
- listing_option: ListingOption = ListingOption.ALL,
52
- ) -> DirListing:
53
- """List files and directories in the given path."""
54
- assert self.path.rclone is not None
55
- dir = Dir(self.path)
56
- return self.path.rclone.ls(
57
- dir,
58
- max_depth=max_depth,
59
- glob=glob,
60
- order=order,
61
- listing_option=listing_option,
62
- )
63
-
64
- def relative_to(self, other: "Dir") -> str:
65
- """Return the relative path to the other directory."""
66
- self_path = Path(self.path.path)
67
- other_path = Path(other.path.path)
68
- rel_path = self_path.relative_to(other_path)
69
- return str(rel_path.as_posix())
70
-
71
- def walk(
72
- self, breadth_first: bool, max_depth: int = -1
73
- ) -> Generator[DirListing, None, None]:
74
- """List files and directories in the given path."""
75
- from rclone_api.detail.walk import walk
76
-
77
- assert self.path.rclone is not None
78
- return walk(self, breadth_first=breadth_first, max_depth=max_depth)
79
-
80
- def to_json(self) -> dict:
81
- """Convert the Dir to a JSON serializable dictionary."""
82
- return self.path.to_json()
83
-
84
- def __str__(self) -> str:
85
- return str(self.path)
86
-
87
- def __repr__(self) -> str:
88
- data = self.path.to_json()
89
- data_str = json.dumps(data)
90
- return data_str
91
-
92
- def to_string(self, include_remote: bool = True) -> str:
93
- """Convert the File to a string."""
94
- out = str(self.path)
95
- if not include_remote:
96
- _, out = out.split(":", 1)
97
- return out
98
-
99
- # / operator
100
- def __truediv__(self, other: str) -> "Dir":
101
- """Join the current path with another path."""
102
- path = Path(self.path.path) / other
103
- rpath = RPath(
104
- self.path.remote,
105
- str(path.as_posix()),
106
- name=other,
107
- size=0,
108
- mime_type="inode/directory",
109
- mod_time="",
110
- is_dir=True,
111
- )
112
- rpath.set_rclone(self.path.rclone)
113
- return Dir(rpath)
1
+ import json
2
+ from pathlib import Path
3
+ from typing import Generator
4
+
5
+ from rclone_api.dir_listing import DirListing
6
+ from rclone_api.remote import Remote
7
+ from rclone_api.rpath import RPath
8
+ from rclone_api.types import ListingOption, Order
9
+
10
+
11
+ class Dir:
12
+ """Remote file dataclass."""
13
+
14
+ @property
15
+ def remote(self) -> Remote:
16
+ return self.path.remote
17
+
18
+ @property
19
+ def name(self) -> str:
20
+ return self.path.name
21
+
22
+ def __init__(self, path: RPath | Remote) -> None:
23
+ """Initialize Dir with either an RPath or Remote.
24
+
25
+ Args:
26
+ path: Either an RPath object or a Remote object
27
+ """
28
+ if isinstance(path, Remote):
29
+ # Need to create an RPath for the Remote's root
30
+ self.path = RPath(
31
+ remote=path,
32
+ path=str(path),
33
+ name=str(path),
34
+ size=0,
35
+ mime_type="inode/directory",
36
+ mod_time="",
37
+ is_dir=True,
38
+ )
39
+ # Ensure the RPath has the same rclone instance as the Remote
40
+ self.path.set_rclone(path.rclone)
41
+ else:
42
+ self.path = path
43
+ # self.path.set_rclone(self.path.remote.rclone)
44
+ assert self.path.rclone is not None
45
+
46
+ def ls(
47
+ self,
48
+ max_depth: int | None = None,
49
+ glob: str | None = None,
50
+ order: Order = Order.NORMAL,
51
+ listing_option: ListingOption = ListingOption.ALL,
52
+ ) -> DirListing:
53
+ """List files and directories in the given path."""
54
+ assert self.path.rclone is not None
55
+ dir = Dir(self.path)
56
+ return self.path.rclone.ls(
57
+ dir,
58
+ max_depth=max_depth,
59
+ glob=glob,
60
+ order=order,
61
+ listing_option=listing_option,
62
+ )
63
+
64
+ def relative_to(self, other: "Dir") -> str:
65
+ """Return the relative path to the other directory."""
66
+ self_path = Path(self.path.path)
67
+ other_path = Path(other.path.path)
68
+ rel_path = self_path.relative_to(other_path)
69
+ return str(rel_path.as_posix())
70
+
71
+ def walk(
72
+ self, breadth_first: bool, max_depth: int = -1
73
+ ) -> Generator[DirListing, None, None]:
74
+ """List files and directories in the given path."""
75
+ from rclone_api.detail.walk import walk
76
+
77
+ assert self.path.rclone is not None
78
+ return walk(self, breadth_first=breadth_first, max_depth=max_depth)
79
+
80
+ def to_json(self) -> dict:
81
+ """Convert the Dir to a JSON serializable dictionary."""
82
+ return self.path.to_json()
83
+
84
+ def __str__(self) -> str:
85
+ return str(self.path)
86
+
87
+ def __repr__(self) -> str:
88
+ data = self.path.to_json()
89
+ data_str = json.dumps(data)
90
+ return data_str
91
+
92
+ def to_string(self, include_remote: bool = True) -> str:
93
+ """Convert the File to a string."""
94
+ out = str(self.path)
95
+ if not include_remote:
96
+ _, out = out.split(":", 1)
97
+ return out
98
+
99
+ # / operator
100
+ def __truediv__(self, other: str) -> "Dir":
101
+ """Join the current path with another path."""
102
+ path = Path(self.path.path) / other
103
+ rpath = RPath(
104
+ self.path.remote,
105
+ str(path.as_posix()),
106
+ name=other,
107
+ size=0,
108
+ mime_type="inode/directory",
109
+ mod_time="",
110
+ is_dir=True,
111
+ )
112
+ rpath.set_rclone(self.path.rclone)
113
+ return Dir(rpath)
rclone_api/log.py CHANGED
@@ -1,44 +1,44 @@
1
- import logging
2
- import sys
3
-
4
- _INITIALISED = False
5
-
6
-
7
- def setup_default_logging():
8
- """Set up default logging configuration if none exists."""
9
- global _INITIALISED
10
- if _INITIALISED:
11
- return
12
- if not logging.root.handlers:
13
- logging.basicConfig(
14
- level=logging.INFO,
15
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
16
- handlers=[
17
- logging.StreamHandler(sys.stdout),
18
- # Uncomment to add file logging
19
- # logging.FileHandler('rclone_api.log')
20
- ],
21
- )
22
-
23
-
24
- def configure_logging(level=logging.INFO, log_file=None):
25
- """Configure logging for the rclone_api package.
26
-
27
- Args:
28
- level: The logging level (default: logging.INFO)
29
- log_file: Optional path to a log file
30
- """
31
- handlers = [logging.StreamHandler(sys.stdout)]
32
- if log_file:
33
- handlers.append(logging.FileHandler(log_file))
34
-
35
- logging.basicConfig(
36
- level=level,
37
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
38
- handlers=handlers,
39
- force=True, # Override any existing configuration
40
- )
41
-
42
-
43
- # Call setup_default_logging when this module is imported
44
- setup_default_logging()
1
+ import logging
2
+ import sys
3
+
4
+ _INITIALISED = False
5
+
6
+
7
+ def setup_default_logging():
8
+ """Set up default logging configuration if none exists."""
9
+ global _INITIALISED
10
+ if _INITIALISED:
11
+ return
12
+ if not logging.root.handlers:
13
+ logging.basicConfig(
14
+ level=logging.INFO,
15
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
16
+ handlers=[
17
+ logging.StreamHandler(sys.stdout),
18
+ # Uncomment to add file logging
19
+ # logging.FileHandler('rclone_api.log')
20
+ ],
21
+ )
22
+
23
+
24
+ def configure_logging(level=logging.INFO, log_file=None):
25
+ """Configure logging for the rclone_api package.
26
+
27
+ Args:
28
+ level: The logging level (default: logging.INFO)
29
+ log_file: Optional path to a log file
30
+ """
31
+ handlers = [logging.StreamHandler(sys.stdout)]
32
+ if log_file:
33
+ handlers.append(logging.FileHandler(log_file))
34
+
35
+ logging.basicConfig(
36
+ level=level,
37
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
38
+ handlers=handlers,
39
+ force=True, # Override any existing configuration
40
+ )
41
+
42
+
43
+ # Call setup_default_logging when this module is imported
44
+ setup_default_logging()