rclone-api 1.0.0__tar.gz → 1.0.2__tar.gz
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-1.0.2/.gitignore +141 -0
- rclone_api-1.0.2/.pylintrc +2 -0
- rclone_api-1.0.2/.vscode/launch.json +44 -0
- rclone_api-1.0.2/.vscode/settings.json +29 -0
- rclone_api-1.0.2/.vscode/tasks.json +41 -0
- {rclone_api-1.0.0/src/rclone_api.egg-info → rclone_api-1.0.2}/PKG-INFO +1 -1
- rclone_api-1.0.2/clean +21 -0
- rclone_api-1.0.2/install +37 -0
- rclone_api-1.0.2/lint +15 -0
- {rclone_api-1.0.0 → rclone_api-1.0.2}/pyproject.toml +1 -1
- rclone_api-1.0.2/src/rclone_api/__init__.py +4 -0
- rclone_api-1.0.2/src/rclone_api/file.py +77 -0
- rclone_api-1.0.2/src/rclone_api/rclone.py +43 -0
- rclone_api-1.0.2/src/rclone_api/types.py +34 -0
- rclone_api-1.0.2/src/rclone_api/util.py +49 -0
- {rclone_api-1.0.0 → rclone_api-1.0.2/src/rclone_api.egg-info}/PKG-INFO +1 -1
- {rclone_api-1.0.0 → rclone_api-1.0.2}/src/rclone_api.egg-info/SOURCES.txt +14 -0
- rclone_api-1.0.2/test +5 -0
- rclone_api-1.0.2/tox.ini +24 -0
- rclone_api-1.0.2/upload_package.sh +10 -0
- rclone_api-1.0.0/src/rclone_api/__init__.py +0 -0
- rclone_api-1.0.0/src/rclone_api/rclone.py +0 -160
- {rclone_api-1.0.0 → rclone_api-1.0.2}/LICENSE +0 -0
- {rclone_api-1.0.0 → rclone_api-1.0.2}/MANIFEST.in +0 -0
- {rclone_api-1.0.0 → rclone_api-1.0.2}/README.md +0 -0
- {rclone_api-1.0.0 → rclone_api-1.0.2}/requirements.testing.txt +0 -0
- {rclone_api-1.0.0 → rclone_api-1.0.2}/setup.cfg +0 -0
- {rclone_api-1.0.0 → rclone_api-1.0.2}/setup.py +0 -0
- {rclone_api-1.0.0 → rclone_api-1.0.2}/src/rclone_api/assets/example.txt +0 -0
- {rclone_api-1.0.0 → rclone_api-1.0.2}/src/rclone_api/cli.py +0 -0
- {rclone_api-1.0.0 → rclone_api-1.0.2}/src/rclone_api.egg-info/dependency_links.txt +0 -0
- {rclone_api-1.0.0 → rclone_api-1.0.2}/src/rclone_api.egg-info/top_level.txt +0 -0
- {rclone_api-1.0.0 → rclone_api-1.0.2}/tests/test_simple.py +0 -0
@@ -0,0 +1,141 @@
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
2
|
+
__pycache__/
|
3
|
+
*.py[cod]
|
4
|
+
*$py.class
|
5
|
+
*.pyc
|
6
|
+
|
7
|
+
# Generated commands
|
8
|
+
/zcmds/bin/
|
9
|
+
|
10
|
+
# C extensions
|
11
|
+
*.so
|
12
|
+
|
13
|
+
# Distribution / packaging
|
14
|
+
.Python
|
15
|
+
prod_env/
|
16
|
+
data/
|
17
|
+
build/
|
18
|
+
develop-eggs/
|
19
|
+
dist/
|
20
|
+
downloads/
|
21
|
+
eggs/
|
22
|
+
.eggs/
|
23
|
+
lib/
|
24
|
+
lib64/
|
25
|
+
parts/
|
26
|
+
sdist/
|
27
|
+
var/
|
28
|
+
wheels/
|
29
|
+
pip-wheel-metadata/
|
30
|
+
share/python-wheels/
|
31
|
+
*.egg-info/
|
32
|
+
.installed.cfg
|
33
|
+
*.egg
|
34
|
+
MANIFEST
|
35
|
+
|
36
|
+
# PyInstaller
|
37
|
+
# Usually these files are written by a python script from a template
|
38
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
39
|
+
*.manifest
|
40
|
+
*.spec
|
41
|
+
|
42
|
+
# Installer logs
|
43
|
+
pip-log.txt
|
44
|
+
pip-delete-this-directory.txt
|
45
|
+
|
46
|
+
# Unit test / coverage reports
|
47
|
+
htmlcov/
|
48
|
+
.tox/
|
49
|
+
.nox/
|
50
|
+
.coverage
|
51
|
+
.coverage.*
|
52
|
+
.cache
|
53
|
+
nosetests.xml
|
54
|
+
coverage.xml
|
55
|
+
*.cover
|
56
|
+
*.py,cover
|
57
|
+
.hypothesis/
|
58
|
+
.pytest_cache/
|
59
|
+
|
60
|
+
# Translations
|
61
|
+
*.mo
|
62
|
+
*.pot
|
63
|
+
|
64
|
+
# Django stuff:
|
65
|
+
*.log
|
66
|
+
local_settings.py
|
67
|
+
db.sqlite3
|
68
|
+
db.sqlite3-journal
|
69
|
+
|
70
|
+
# Flask stuff:
|
71
|
+
instance/
|
72
|
+
.webassets-cache
|
73
|
+
|
74
|
+
# Scrapy stuff:
|
75
|
+
.scrapy
|
76
|
+
|
77
|
+
# Sphinx documentation
|
78
|
+
docs/_build/
|
79
|
+
|
80
|
+
# PyBuilder
|
81
|
+
target/
|
82
|
+
|
83
|
+
# Jupyter Notebook
|
84
|
+
.ipynb_checkpoints
|
85
|
+
|
86
|
+
# IPython
|
87
|
+
profile_default/
|
88
|
+
ipython_config.py
|
89
|
+
|
90
|
+
# pyenv
|
91
|
+
.python-version
|
92
|
+
|
93
|
+
# pipenv
|
94
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
95
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
96
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
97
|
+
# install all needed dependencies.
|
98
|
+
#Pipfile.lock
|
99
|
+
|
100
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
101
|
+
__pypackages__/
|
102
|
+
|
103
|
+
# Celery stuff
|
104
|
+
celerybeat-schedule
|
105
|
+
celerybeat.pid
|
106
|
+
|
107
|
+
# SageMath parsed files
|
108
|
+
*.sage.py
|
109
|
+
|
110
|
+
# Environments
|
111
|
+
.env
|
112
|
+
.venv
|
113
|
+
env/
|
114
|
+
venv/
|
115
|
+
ENV/
|
116
|
+
env.bak/
|
117
|
+
venv.bak/
|
118
|
+
|
119
|
+
# Spyder project settings
|
120
|
+
.spyderproject
|
121
|
+
.spyproject
|
122
|
+
|
123
|
+
# Rope project settings
|
124
|
+
.ropeproject
|
125
|
+
|
126
|
+
# mkdocs documentation
|
127
|
+
/site
|
128
|
+
|
129
|
+
# mypy
|
130
|
+
.mypy_cache/
|
131
|
+
.dmypy.json
|
132
|
+
dmypy.json
|
133
|
+
|
134
|
+
# Pyre type checker
|
135
|
+
.pyre/
|
136
|
+
.activate.sh
|
137
|
+
activate
|
138
|
+
|
139
|
+
# Generated by mac
|
140
|
+
**/.DS_Store
|
141
|
+
uv.lock
|
@@ -0,0 +1,2 @@
|
|
1
|
+
[MESSAGES CONTROL]
|
2
|
+
disable=missing-module-docstring,missing-class-docstring,missing-function-docstring,line-too-long,raise-missing-from,too-few-public-methods,too-many-return-statements,fixme,too-many-locals,too-many-branches,too-many-statements,too-many-arguments,too-many-instance-attributes,too-many-ancestors,too-many-lines,too-many-public-methods,too-many-boolean-expressions,too-many-locals,too-many-branches,too-many-statements,too-many-arguments,too-many-instance-attributes,too-many-ancestors,too-many-lines,too-many-public-methods,too-many-boolean-expressions,R0801
|
@@ -0,0 +1,44 @@
|
|
1
|
+
{
|
2
|
+
// Use IntelliSense to learn about possible attributes.
|
3
|
+
// Hover to view descriptions of existing attributes.
|
4
|
+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
5
|
+
"version": "0.2.0",
|
6
|
+
"configurations": [
|
7
|
+
{
|
8
|
+
"name": "Python: Current File (Integrated Terminal)",
|
9
|
+
"type": "python",
|
10
|
+
"request": "launch",
|
11
|
+
"subProcess": true,
|
12
|
+
"program": "${file}",
|
13
|
+
"console": "integratedTerminal",
|
14
|
+
"justMyCode": false
|
15
|
+
},
|
16
|
+
{
|
17
|
+
"name": "Python: Remote Attach",
|
18
|
+
"type": "python",
|
19
|
+
"request": "attach",
|
20
|
+
"port": 5678,
|
21
|
+
"host": "localhost",
|
22
|
+
"pathMappings": [
|
23
|
+
{
|
24
|
+
"localRoot": "${workspaceFolder}",
|
25
|
+
"remoteRoot": "."
|
26
|
+
}
|
27
|
+
]
|
28
|
+
},
|
29
|
+
{
|
30
|
+
"name": "Python: Module",
|
31
|
+
"type": "python",
|
32
|
+
"request": "launch",
|
33
|
+
"module": "enter-your-module-name-here",
|
34
|
+
"console": "integratedTerminal"
|
35
|
+
},
|
36
|
+
{
|
37
|
+
"name": "Python: Current File (External Terminal)",
|
38
|
+
"type": "python",
|
39
|
+
"request": "launch",
|
40
|
+
"program": "${file}",
|
41
|
+
"console": "externalTerminal"
|
42
|
+
}
|
43
|
+
]
|
44
|
+
}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
{
|
2
|
+
"python.autoComplete.extraPaths": [
|
3
|
+
"."
|
4
|
+
],
|
5
|
+
"python.linting.pylintEnabled": true,
|
6
|
+
"python.linting.enabled": true,
|
7
|
+
"terminal.integrated.defaultProfile.windows": "Git Bash",
|
8
|
+
"terminal.integrated.profiles.windows": {
|
9
|
+
"Git Bash": {
|
10
|
+
"path": "C:\\Program Files\\Git\\bin\\bash.exe",
|
11
|
+
"args": ["--cd=."]
|
12
|
+
}
|
13
|
+
},
|
14
|
+
// adds activate virtualenv to terminal
|
15
|
+
"terminal.integrated.env.windows": {
|
16
|
+
"VIRTUAL_ENV": "${workspaceFolder}/venv"
|
17
|
+
},
|
18
|
+
"files.eol": "\n", // Unix
|
19
|
+
"editor.tabSize": 4,
|
20
|
+
"editor.insertSpaces": true,
|
21
|
+
"editor.detectIndentation": false,
|
22
|
+
"editor.formatOnSave": false,
|
23
|
+
"python.formatting.provider": "black",
|
24
|
+
"python.formatting.blackArgs": [
|
25
|
+
],
|
26
|
+
"python.analysis.extraPaths": [
|
27
|
+
"."
|
28
|
+
]
|
29
|
+
}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
{
|
2
|
+
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
3
|
+
// for the documentation about the tasks.json format
|
4
|
+
"version": "2.0.0",
|
5
|
+
"tasks": [
|
6
|
+
{
|
7
|
+
"label": "Lint",
|
8
|
+
"type": "shell",
|
9
|
+
"command": ". ./activate.sh && ./lint.sh",
|
10
|
+
"group": "build",
|
11
|
+
"options": {
|
12
|
+
"cwd": "${workspaceRoot}"
|
13
|
+
},
|
14
|
+
"presentation": {
|
15
|
+
"echo": true,
|
16
|
+
"reveal": "always",
|
17
|
+
"focus": true,
|
18
|
+
"panel": "shared",
|
19
|
+
"clear": true
|
20
|
+
},
|
21
|
+
"problemMatcher": []
|
22
|
+
},
|
23
|
+
{
|
24
|
+
"label": "Tox",
|
25
|
+
"type": "shell",
|
26
|
+
"command": "tox",
|
27
|
+
"group": "build",
|
28
|
+
"options": {
|
29
|
+
"cwd": "${workspaceRoot}"
|
30
|
+
},
|
31
|
+
"presentation": {
|
32
|
+
"echo": true,
|
33
|
+
"reveal": "always",
|
34
|
+
"focus": true,
|
35
|
+
"panel": "shared",
|
36
|
+
"clear": true
|
37
|
+
},
|
38
|
+
"problemMatcher": []
|
39
|
+
},
|
40
|
+
]
|
41
|
+
}
|
rclone_api-1.0.2/clean
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
set -x
|
3
|
+
|
4
|
+
rm -rf build
|
5
|
+
rm -rf dist
|
6
|
+
rm -rf venv
|
7
|
+
rm -rf .venv
|
8
|
+
|
9
|
+
rm -rf *.egg-info
|
10
|
+
rm -rf .eggs
|
11
|
+
rm -rf .tox
|
12
|
+
rm -rf .cache
|
13
|
+
rm -rf .pytest_cache
|
14
|
+
rm -rf .mypy_cache
|
15
|
+
rm -rf .coverage
|
16
|
+
|
17
|
+
# remove all *.pyc files
|
18
|
+
find . -name "*.pyc" -exec rm -rf {} \;
|
19
|
+
# remove all *.egg files
|
20
|
+
find . -name "*.egg" -exec rm -rf {} \;
|
21
|
+
|
rclone_api-1.0.2/install
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
set -e
|
3
|
+
|
4
|
+
# Check if UV is not found
|
5
|
+
if ! command -v uv &> /dev/null; then
|
6
|
+
# If Darwin (macOS), use brew to install UV
|
7
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
8
|
+
brew install uv
|
9
|
+
else
|
10
|
+
# If it's Windows, use pip to install UV, else use pip3
|
11
|
+
if [[ "$OSTYPE" == "msys" ]]; then
|
12
|
+
pip install uv
|
13
|
+
else
|
14
|
+
pip3 install uv
|
15
|
+
fi
|
16
|
+
fi
|
17
|
+
fi
|
18
|
+
|
19
|
+
uv venv --python 3.11 --seed
|
20
|
+
uv run pip install -e .
|
21
|
+
|
22
|
+
# If requirements.testing.txt exists, then install it
|
23
|
+
if [[ -f requirements.testing.txt ]]; then
|
24
|
+
uv run pip install -r requirements.testing.txt
|
25
|
+
fi
|
26
|
+
|
27
|
+
# If activate exists, delete it
|
28
|
+
if [[ -f activate ]]; then
|
29
|
+
rm activate
|
30
|
+
fi
|
31
|
+
|
32
|
+
# If Windows, then symlink .venv/Scripts/activate to .venv/bin/activate
|
33
|
+
if [[ "$OSTYPE" == "msys" ]]; then
|
34
|
+
ln -s .venv/Scripts/activate ./activate
|
35
|
+
else
|
36
|
+
ln -s .venv/bin/activate ./activate
|
37
|
+
fi
|
rclone_api-1.0.2/lint
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
set -e
|
3
|
+
|
4
|
+
echo Running ruff src
|
5
|
+
uv run ruff check --fix src
|
6
|
+
echo Running ruff tests
|
7
|
+
uv run ruff check --fix tests
|
8
|
+
echo Running black src tests
|
9
|
+
uv run black src tests
|
10
|
+
echo Running isort src tests
|
11
|
+
uv run isort --profile black src tests
|
12
|
+
echo Running mypy src
|
13
|
+
uv run mypy src tests
|
14
|
+
echo Linting complete!
|
15
|
+
exit 0
|
@@ -0,0 +1,77 @@
|
|
1
|
+
import json
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
|
5
|
+
class File:
|
6
|
+
"""Remote file dataclass."""
|
7
|
+
|
8
|
+
def __init__(
|
9
|
+
self,
|
10
|
+
path: str,
|
11
|
+
name: str,
|
12
|
+
size: int,
|
13
|
+
mime_type: str,
|
14
|
+
mod_time: str,
|
15
|
+
is_dir: bool,
|
16
|
+
) -> None:
|
17
|
+
from rclone_api.rclone import Rclone
|
18
|
+
|
19
|
+
self.path = path
|
20
|
+
self.name = name
|
21
|
+
self.size = size
|
22
|
+
self.mime_type = mime_type
|
23
|
+
self.mod_time = mod_time
|
24
|
+
self.is_dir = is_dir
|
25
|
+
self.rclone: Rclone | None = None
|
26
|
+
|
27
|
+
def set_rclone(self, rclone: Any) -> None:
|
28
|
+
"""Set the rclone object."""
|
29
|
+
from rclone_api.rclone import Rclone
|
30
|
+
|
31
|
+
assert isinstance(rclone, Rclone)
|
32
|
+
self.rclone = rclone
|
33
|
+
|
34
|
+
@staticmethod
|
35
|
+
def from_dict(data: dict) -> "File":
|
36
|
+
"""Create a File from a dictionary."""
|
37
|
+
return File(
|
38
|
+
data["Path"],
|
39
|
+
data["Name"],
|
40
|
+
data["Size"],
|
41
|
+
data["MimeType"],
|
42
|
+
data["ModTime"],
|
43
|
+
data["IsDir"],
|
44
|
+
# data["IsBucket"],
|
45
|
+
)
|
46
|
+
|
47
|
+
@staticmethod
|
48
|
+
def from_array(data: list[dict]) -> list["File"]:
|
49
|
+
"""Create a File from a dictionary."""
|
50
|
+
out: list[File] = []
|
51
|
+
for d in data:
|
52
|
+
file: File = File.from_dict(d)
|
53
|
+
out.append(file)
|
54
|
+
return out
|
55
|
+
|
56
|
+
@staticmethod
|
57
|
+
def from_json_str(json_str: str) -> list["File"]:
|
58
|
+
"""Create a File from a JSON string."""
|
59
|
+
json_obj = json.loads(json_str)
|
60
|
+
if isinstance(json_obj, dict):
|
61
|
+
return [File.from_dict(json_obj)]
|
62
|
+
return File.from_array(json_obj)
|
63
|
+
|
64
|
+
def to_json(self) -> dict:
|
65
|
+
return {
|
66
|
+
"Path": self.path,
|
67
|
+
"Name": self.name,
|
68
|
+
"Size": self.size,
|
69
|
+
"MimeType": self.mime_type,
|
70
|
+
"ModTime": self.mod_time,
|
71
|
+
"IsDir": self.is_dir,
|
72
|
+
# "IsBucket": self.is_bucket,
|
73
|
+
}
|
74
|
+
|
75
|
+
def __str__(self) -> str:
|
76
|
+
out = self.to_json()
|
77
|
+
return json.dumps(out)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
"""
|
2
|
+
Unit test file.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import subprocess
|
6
|
+
from pathlib import Path
|
7
|
+
|
8
|
+
from rclone_api.file import File
|
9
|
+
from rclone_api.types import Config, RcloneExec, Remote
|
10
|
+
from rclone_api.util import get_rclone_exe
|
11
|
+
|
12
|
+
|
13
|
+
class Rclone:
|
14
|
+
def __init__(
|
15
|
+
self, rclone_conf: Path | Config, rclone_exe: Path | None = None
|
16
|
+
) -> None:
|
17
|
+
if isinstance(rclone_conf, Path):
|
18
|
+
if not rclone_conf.exists():
|
19
|
+
raise ValueError(f"Rclone config file not found: {rclone_conf}")
|
20
|
+
self._exec = RcloneExec(rclone_conf, get_rclone_exe(rclone_exe))
|
21
|
+
|
22
|
+
def _run(self, cmd: list[str]) -> subprocess.CompletedProcess:
|
23
|
+
return self._exec.execute(cmd)
|
24
|
+
|
25
|
+
def ls(self, path: str | Remote) -> list[File]:
|
26
|
+
cmd = ["lsjson", str(path)]
|
27
|
+
cp = self._run(cmd)
|
28
|
+
text = cp.stdout
|
29
|
+
out: list[File] = File.from_json_str(text)
|
30
|
+
for o in out:
|
31
|
+
o.set_rclone(self)
|
32
|
+
return out
|
33
|
+
|
34
|
+
def listremotes(self) -> list[Remote]:
|
35
|
+
cmd = ["listremotes"]
|
36
|
+
cp = self._run(cmd)
|
37
|
+
text: str = cp.stdout
|
38
|
+
tmp = text.splitlines()
|
39
|
+
tmp = [t.strip() for t in tmp]
|
40
|
+
# strip out ":" from the end
|
41
|
+
tmp = [t.replace(":", "") for t in tmp]
|
42
|
+
out = [Remote(name=t) for t in tmp]
|
43
|
+
return out
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import subprocess
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
|
6
|
+
@dataclass
|
7
|
+
class Config:
|
8
|
+
"""Rclone configuration dataclass."""
|
9
|
+
|
10
|
+
text: str
|
11
|
+
|
12
|
+
|
13
|
+
@dataclass
|
14
|
+
class RcloneExec:
|
15
|
+
"""Rclone execution dataclass."""
|
16
|
+
|
17
|
+
rclone_config: Path | Config
|
18
|
+
rclone_exe: Path
|
19
|
+
|
20
|
+
def execute(self, cmd: list[str]) -> subprocess.CompletedProcess:
|
21
|
+
"""Execute rclone command."""
|
22
|
+
from rclone_api.util import rclone_execute
|
23
|
+
|
24
|
+
return rclone_execute(cmd, self.rclone_config, self.rclone_exe)
|
25
|
+
|
26
|
+
|
27
|
+
class Remote:
|
28
|
+
"""Remote dataclass."""
|
29
|
+
|
30
|
+
def __init__(self, name: str) -> None:
|
31
|
+
self.name = name
|
32
|
+
|
33
|
+
def __str__(self) -> str:
|
34
|
+
return f"{self.name}:"
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import shutil
|
2
|
+
import subprocess
|
3
|
+
from pathlib import Path
|
4
|
+
from tempfile import TemporaryDirectory
|
5
|
+
|
6
|
+
from rclone_api.types import Config
|
7
|
+
|
8
|
+
|
9
|
+
def get_rclone_exe(rclone_exe: Path | None) -> Path:
|
10
|
+
if rclone_exe is None:
|
11
|
+
|
12
|
+
rclone_which_path = shutil.which("rclone")
|
13
|
+
if rclone_which_path is None:
|
14
|
+
raise ValueError("rclone executable not found")
|
15
|
+
return Path(rclone_which_path)
|
16
|
+
return rclone_exe
|
17
|
+
|
18
|
+
|
19
|
+
def rclone_execute(
|
20
|
+
cmd: list[str],
|
21
|
+
rclone_conf: Path | Config,
|
22
|
+
rclone_exe: Path,
|
23
|
+
verbose: bool = False,
|
24
|
+
) -> subprocess.CompletedProcess:
|
25
|
+
print(subprocess.list2cmdline(cmd))
|
26
|
+
tempdir: TemporaryDirectory | None = None
|
27
|
+
|
28
|
+
try:
|
29
|
+
if isinstance(rclone_conf, Config):
|
30
|
+
tempdir = TemporaryDirectory()
|
31
|
+
tmpfile = Path(tempdir.name) / "rclone.conf"
|
32
|
+
tmpfile.write_text(rclone_conf.text, encoding="utf-8")
|
33
|
+
rclone_conf = tmpfile
|
34
|
+
cmd = (
|
35
|
+
[str(rclone_exe.resolve())] + ["--config", str(rclone_conf.resolve())] + cmd
|
36
|
+
)
|
37
|
+
if verbose:
|
38
|
+
cmd_str = subprocess.list2cmdline(cmd)
|
39
|
+
print(f"Running: {cmd_str}")
|
40
|
+
cp = subprocess.run(
|
41
|
+
cmd, capture_output=True, encoding="utf-8", check=True, shell=False
|
42
|
+
)
|
43
|
+
return cp
|
44
|
+
finally:
|
45
|
+
if tempdir:
|
46
|
+
try:
|
47
|
+
tempdir.cleanup()
|
48
|
+
except Exception as e:
|
49
|
+
print(f"Error cleaning up tempdir: {e}")
|
@@ -1,12 +1,26 @@
|
|
1
|
+
.gitignore
|
2
|
+
.pylintrc
|
1
3
|
LICENSE
|
2
4
|
MANIFEST.in
|
3
5
|
README.md
|
6
|
+
clean
|
7
|
+
install
|
8
|
+
lint
|
4
9
|
pyproject.toml
|
5
10
|
requirements.testing.txt
|
6
11
|
setup.py
|
12
|
+
test
|
13
|
+
tox.ini
|
14
|
+
upload_package.sh
|
15
|
+
.vscode/launch.json
|
16
|
+
.vscode/settings.json
|
17
|
+
.vscode/tasks.json
|
7
18
|
src/rclone_api/__init__.py
|
8
19
|
src/rclone_api/cli.py
|
20
|
+
src/rclone_api/file.py
|
9
21
|
src/rclone_api/rclone.py
|
22
|
+
src/rclone_api/types.py
|
23
|
+
src/rclone_api/util.py
|
10
24
|
src/rclone_api.egg-info/PKG-INFO
|
11
25
|
src/rclone_api.egg-info/SOURCES.txt
|
12
26
|
src/rclone_api.egg-info/dependency_links.txt
|
rclone_api-1.0.2/test
ADDED
rclone_api-1.0.2/tox.ini
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# content of: tox.ini , put in same dir as setup.py
|
2
|
+
[tox]
|
3
|
+
envlist = py310
|
4
|
+
|
5
|
+
[gh-actions]
|
6
|
+
python =
|
7
|
+
3.10: py310
|
8
|
+
|
9
|
+
[flake8]
|
10
|
+
per-file-ignores = __init__.py:F401
|
11
|
+
ignore = E501, E203, W503
|
12
|
+
|
13
|
+
[testenv]
|
14
|
+
# install pytest in the virtualenv where commands will be executed
|
15
|
+
deps =
|
16
|
+
-r{toxinidir}/requirements.testing.txt
|
17
|
+
commands =
|
18
|
+
# NOTE: you can run any command line tool here - not just tests
|
19
|
+
ruff src
|
20
|
+
ruff tests
|
21
|
+
flake8 src tests
|
22
|
+
pylint src tests
|
23
|
+
mypy src tests
|
24
|
+
pytest tests -n auto -vv
|
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
set -e
|
3
|
+
rm -rf build dist
|
4
|
+
. ./activate
|
5
|
+
pip install wheel twine
|
6
|
+
echo "Building Source and Wheel (universal) distribution…"
|
7
|
+
python setup.py sdist bdist_wheel --universal
|
8
|
+
echo "Uploading the package to PyPI via Twine…"
|
9
|
+
twine upload dist/* --verbose
|
10
|
+
# echo Pushing git tags…
|
File without changes
|
@@ -1,160 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Unit test file.
|
3
|
-
"""
|
4
|
-
|
5
|
-
import json
|
6
|
-
import shutil
|
7
|
-
import subprocess
|
8
|
-
from dataclasses import dataclass
|
9
|
-
from pathlib import Path
|
10
|
-
from tempfile import TemporaryDirectory
|
11
|
-
|
12
|
-
|
13
|
-
@dataclass
|
14
|
-
class RcloneConfig:
|
15
|
-
"""Rclone configuration dataclass."""
|
16
|
-
|
17
|
-
text: str
|
18
|
-
|
19
|
-
|
20
|
-
def _rclone_execute(
|
21
|
-
cmd: list[str],
|
22
|
-
rclone_conf: Path | RcloneConfig,
|
23
|
-
rclone_exe: Path,
|
24
|
-
verbose: bool = False,
|
25
|
-
) -> subprocess.CompletedProcess:
|
26
|
-
print(subprocess.list2cmdline(cmd))
|
27
|
-
tempdir: TemporaryDirectory | None = None
|
28
|
-
|
29
|
-
try:
|
30
|
-
if isinstance(rclone_conf, RcloneConfig):
|
31
|
-
tempdir = TemporaryDirectory()
|
32
|
-
tmpfile = Path(tempdir.name) / "rclone.conf"
|
33
|
-
tmpfile.write_text(rclone_conf.text, encoding="utf-8")
|
34
|
-
rclone_conf = tmpfile
|
35
|
-
cmd = (
|
36
|
-
[str(rclone_exe.resolve())] + ["--config", str(rclone_conf.resolve())] + cmd
|
37
|
-
)
|
38
|
-
if verbose:
|
39
|
-
cmd_str = subprocess.list2cmdline(cmd)
|
40
|
-
print(f"Running: {cmd_str}")
|
41
|
-
cp = subprocess.run(
|
42
|
-
cmd, capture_output=True, encoding="utf-8", check=True, shell=False
|
43
|
-
)
|
44
|
-
return cp
|
45
|
-
finally:
|
46
|
-
if tempdir:
|
47
|
-
try:
|
48
|
-
tempdir.cleanup()
|
49
|
-
except Exception as e:
|
50
|
-
print(f"Error cleaning up tempdir: {e}")
|
51
|
-
|
52
|
-
|
53
|
-
@dataclass
|
54
|
-
class RcloneExec:
|
55
|
-
"""Rclone execution dataclass."""
|
56
|
-
|
57
|
-
rclone_config: Path | RcloneConfig
|
58
|
-
rclone_exe: Path
|
59
|
-
|
60
|
-
def execute(self, cmd: list[str]) -> subprocess.CompletedProcess:
|
61
|
-
"""Execute rclone command."""
|
62
|
-
return _rclone_execute(cmd, self.rclone_config, self.rclone_exe)
|
63
|
-
|
64
|
-
|
65
|
-
@dataclass
|
66
|
-
class RemoteFile:
|
67
|
-
"""Remote file dataclass."""
|
68
|
-
|
69
|
-
path: str
|
70
|
-
name: str
|
71
|
-
size: int
|
72
|
-
mime_type: str
|
73
|
-
mod_time: str
|
74
|
-
is_dir: bool
|
75
|
-
# is_bucket: bool
|
76
|
-
|
77
|
-
@staticmethod
|
78
|
-
def from_dict(data: dict) -> "RemoteFile":
|
79
|
-
"""Create a RemoteFile from a dictionary."""
|
80
|
-
return RemoteFile(
|
81
|
-
data["Path"],
|
82
|
-
data["Name"],
|
83
|
-
data["Size"],
|
84
|
-
data["MimeType"],
|
85
|
-
data["ModTime"],
|
86
|
-
data["IsDir"],
|
87
|
-
# data["IsBucket"],
|
88
|
-
)
|
89
|
-
|
90
|
-
@staticmethod
|
91
|
-
def from_array(data: list[dict]) -> list["RemoteFile"]:
|
92
|
-
"""Create a RemoteFile from a dictionary."""
|
93
|
-
out: list[RemoteFile] = []
|
94
|
-
for d in data:
|
95
|
-
file: RemoteFile = RemoteFile.from_dict(d)
|
96
|
-
out.append(file)
|
97
|
-
return out
|
98
|
-
|
99
|
-
@staticmethod
|
100
|
-
def from_json_str(json_str: str) -> list["RemoteFile"]:
|
101
|
-
"""Create a RemoteFile from a JSON string."""
|
102
|
-
json_obj = json.loads(json_str)
|
103
|
-
if isinstance(json_obj, dict):
|
104
|
-
return [RemoteFile.from_dict(json_obj)]
|
105
|
-
return RemoteFile.from_array(json_obj)
|
106
|
-
|
107
|
-
def to_json(self) -> dict:
|
108
|
-
return {
|
109
|
-
"Path": self.path,
|
110
|
-
"Name": self.name,
|
111
|
-
"Size": self.size,
|
112
|
-
"MimeType": self.mime_type,
|
113
|
-
"ModTime": self.mod_time,
|
114
|
-
"IsDir": self.is_dir,
|
115
|
-
# "IsBucket": self.is_bucket,
|
116
|
-
}
|
117
|
-
|
118
|
-
def __str__(self) -> str:
|
119
|
-
out = self.to_json()
|
120
|
-
return json.dumps(out)
|
121
|
-
|
122
|
-
|
123
|
-
def _get_rclone_exe(rclone_exe: Path | None) -> Path:
|
124
|
-
if rclone_exe is None:
|
125
|
-
|
126
|
-
rclone_which_path = shutil.which("rclone")
|
127
|
-
if rclone_which_path is None:
|
128
|
-
raise ValueError("rclone executable not found")
|
129
|
-
return Path(rclone_which_path)
|
130
|
-
return rclone_exe
|
131
|
-
|
132
|
-
|
133
|
-
class Rclone:
|
134
|
-
def __init__(
|
135
|
-
self, rclone_conf: Path | RcloneConfig, rclone_exe: Path | None = None
|
136
|
-
) -> None:
|
137
|
-
if isinstance(rclone_conf, Path):
|
138
|
-
if not rclone_conf.exists():
|
139
|
-
raise ValueError(f"Rclone config file not found: {rclone_conf}")
|
140
|
-
self._exec = RcloneExec(rclone_conf, _get_rclone_exe(rclone_exe))
|
141
|
-
|
142
|
-
def _run(self, cmd: list[str]) -> subprocess.CompletedProcess:
|
143
|
-
return self._exec.execute(cmd)
|
144
|
-
|
145
|
-
def ls(self, path: str) -> list[RemoteFile]:
|
146
|
-
cmd = ["lsjson", path]
|
147
|
-
cp = self._run(cmd)
|
148
|
-
text = cp.stdout
|
149
|
-
out: list[RemoteFile] = RemoteFile.from_json_str(text)
|
150
|
-
return out
|
151
|
-
|
152
|
-
def listremotes(self) -> list[str]:
|
153
|
-
cmd = ["listremotes"]
|
154
|
-
cp = self._run(cmd)
|
155
|
-
text = cp.stdout
|
156
|
-
out = text.splitlines()
|
157
|
-
out = [o.strip() for o in out]
|
158
|
-
# strip out ":" from the end
|
159
|
-
out = [o.replace(":", "") for o in out]
|
160
|
-
return out
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|