rclone-api 1.5.41__py3-none-any.whl → 1.5.43__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/__init__.py +1003 -1003
- rclone_api/config.py +13 -12
- rclone_api/db/__init__.py +3 -3
- rclone_api/detail/walk.py +116 -116
- rclone_api/dir.py +113 -113
- rclone_api/fs.py +2 -2
- rclone_api/log.py +44 -44
- rclone_api/rclone_impl.py +1360 -1360
- rclone_api/s3/multipart/upload_parts_server_side_merge.py +546 -546
- rclone_api/scan_missing_folders.py +153 -153
- {rclone_api-1.5.41.dist-info → rclone_api-1.5.43.dist-info}/METADATA +1108 -1108
- {rclone_api-1.5.41.dist-info → rclone_api-1.5.43.dist-info}/RECORD +16 -16
- {rclone_api-1.5.41.dist-info → rclone_api-1.5.43.dist-info}/WHEEL +1 -1
- {rclone_api-1.5.41.dist-info → rclone_api-1.5.43.dist-info}/entry_points.txt +0 -0
- {rclone_api-1.5.41.dist-info → rclone_api-1.5.43.dist-info}/licenses/LICENSE +0 -0
- {rclone_api-1.5.41.dist-info → rclone_api-1.5.43.dist-info}/top_level.txt +0 -0
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":
|
@@ -60,16 +71,6 @@ class Config:
|
|
60
71
|
def parse(self) -> Parsed:
|
61
72
|
return Parsed.parse(self.text)
|
62
73
|
|
63
|
-
def _auto_convert_if_json(self) -> None:
|
64
|
-
try:
|
65
|
-
text = _json_to_rclone_config_str_or_raise(self.text)
|
66
|
-
self.text = text
|
67
|
-
except Exception:
|
68
|
-
pass
|
69
|
-
|
70
|
-
def __post_init__(self) -> None:
|
71
|
-
self._auto_convert_if_json()
|
72
|
-
|
73
74
|
|
74
75
|
def find_conf_file(rclone: Any | None = None) -> Path | None:
|
75
76
|
import subprocess
|
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/fs.py
CHANGED
@@ -95,9 +95,9 @@ class RemoteFS(FS):
|
|
95
95
|
|
96
96
|
@staticmethod
|
97
97
|
def from_rclone_config(
|
98
|
-
src: str, rclone_conf: Path | Config | str | None
|
98
|
+
src: str, rclone_conf: Path | Config | str | dict | None
|
99
99
|
) -> "RemoteFS":
|
100
|
-
if isinstance(rclone_conf, str):
|
100
|
+
if isinstance(rclone_conf, str) or isinstance(rclone_conf, dict):
|
101
101
|
rclone_conf = Config(text=rclone_conf)
|
102
102
|
return RemoteFS(rclone_conf, src)
|
103
103
|
|
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()
|