b2-cleanup 0.1.0__py3-none-any.whl → 0.1.4__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.
- b2_cleanup/__init__.py +6 -0
- b2_cleanup/cli.py +38 -0
- cleanup_unfinished_b2_uploads.py → b2_cleanup/core.py +30 -42
- b2_cleanup-0.1.4.dist-info/METADATA +196 -0
- b2_cleanup-0.1.4.dist-info/RECORD +7 -0
- {b2_cleanup-0.1.0.dist-info → b2_cleanup-0.1.4.dist-info}/WHEEL +1 -2
- b2_cleanup-0.1.4.dist-info/entry_points.txt +2 -0
- b2_cleanup-0.1.0.dist-info/METADATA +0 -7
- b2_cleanup-0.1.0.dist-info/RECORD +0 -6
- b2_cleanup-0.1.0.dist-info/entry_points.txt +0 -2
- b2_cleanup-0.1.0.dist-info/top_level.txt +0 -1
b2_cleanup/__init__.py
ADDED
b2_cleanup/cli.py
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
"""Command-line interface for B2 cleanup tool."""
|
2
|
+
|
3
|
+
import logging
|
4
|
+
import click
|
5
|
+
from .core import B2CleanupTool
|
6
|
+
|
7
|
+
|
8
|
+
@click.command()
|
9
|
+
@click.argument("bucket")
|
10
|
+
@click.option(
|
11
|
+
"--dry-run", is_flag=True, help="Only list unfinished uploads, do not cancel."
|
12
|
+
)
|
13
|
+
@click.option("--log-file", default="b2_cleanup.log", help="Path to log file.")
|
14
|
+
@click.option("--key-id", help="Backblaze B2 applicationKeyId to override env/config.")
|
15
|
+
@click.option("--key", help="Backblaze B2 applicationKey to override env/config.")
|
16
|
+
def cli(bucket, dry_run, log_file, key_id, key):
|
17
|
+
"""Clean up unfinished large file uploads in BUCKET."""
|
18
|
+
logger = logging.getLogger()
|
19
|
+
logger.setLevel(logging.INFO)
|
20
|
+
if logger.hasHandlers():
|
21
|
+
logger.handlers.clear()
|
22
|
+
|
23
|
+
file_handler = logging.FileHandler(log_file)
|
24
|
+
file_handler.setFormatter(
|
25
|
+
logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
|
26
|
+
)
|
27
|
+
logger.addHandler(file_handler)
|
28
|
+
|
29
|
+
stream_handler = logging.StreamHandler()
|
30
|
+
stream_handler.setFormatter(logging.Formatter("%(message)s"))
|
31
|
+
logger.addHandler(stream_handler)
|
32
|
+
|
33
|
+
tool = B2CleanupTool(dry_run=dry_run, override_key_id=key_id, override_key=key)
|
34
|
+
tool.cleanup_unfinished_uploads(bucket)
|
35
|
+
|
36
|
+
|
37
|
+
if __name__ == "__main__":
|
38
|
+
cli() # pragma: no cover
|
@@ -1,18 +1,28 @@
|
|
1
|
+
"""Core functionality for B2 cleanup tool."""
|
2
|
+
|
1
3
|
import os
|
2
4
|
import json
|
3
5
|
import subprocess
|
4
6
|
import logging
|
5
|
-
import click
|
6
7
|
from b2sdk.v2 import InMemoryAccountInfo, B2Api
|
7
8
|
|
8
9
|
|
9
10
|
class B2CleanupTool:
|
11
|
+
"""Tool to clean up unfinished large file uploads in B2 buckets."""
|
12
|
+
|
10
13
|
def __init__(
|
11
14
|
self,
|
12
15
|
dry_run: bool = False,
|
13
16
|
override_key_id: str = None,
|
14
17
|
override_key: str = None,
|
15
18
|
):
|
19
|
+
"""Initialize the B2 cleanup tool.
|
20
|
+
|
21
|
+
Args:
|
22
|
+
dry_run: If True, only list uploads but don't delete them
|
23
|
+
override_key_id: Optional B2 key ID to override env/config
|
24
|
+
override_key: Optional B2 application key to override env/config
|
25
|
+
"""
|
16
26
|
self.dry_run = dry_run
|
17
27
|
self.logger = logging.getLogger("B2Cleanup")
|
18
28
|
self.api = self._authorize(override_key_id, override_key)
|
@@ -36,12 +46,17 @@ class B2CleanupTool:
|
|
36
46
|
|
37
47
|
try:
|
38
48
|
self.logger.info("🔍 Trying to load credentials via `b2 account get`...")
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
49
|
+
try:
|
50
|
+
result = subprocess.run(
|
51
|
+
["b2", "account", "get"],
|
52
|
+
check=True,
|
53
|
+
capture_output=True,
|
54
|
+
text=True,
|
55
|
+
)
|
56
|
+
except FileNotFoundError:
|
57
|
+
self.logger.error("❌ Command 'b2' not found. Please install the B2 CLI or provide credentials.")
|
58
|
+
raise RuntimeError("B2 CLI not found. Please install it or provide credentials manually.")
|
59
|
+
|
45
60
|
creds = json.loads(result.stdout)
|
46
61
|
key_id = creds["applicationKeyId"]
|
47
62
|
app_key = creds["applicationKey"]
|
@@ -56,6 +71,11 @@ class B2CleanupTool:
|
|
56
71
|
raise RuntimeError("Could not authorize with Backblaze B2.")
|
57
72
|
|
58
73
|
def cleanup_unfinished_uploads(self, bucket_name: str):
|
74
|
+
"""Find and clean up unfinished uploads in the specified bucket.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
bucket_name: Name of the B2 bucket to clean up
|
78
|
+
"""
|
59
79
|
bucket = self.api.get_bucket_by_name(bucket_name)
|
60
80
|
unfinished = list(bucket.list_unfinished_large_files())
|
61
81
|
if not unfinished:
|
@@ -64,43 +84,11 @@ class B2CleanupTool:
|
|
64
84
|
|
65
85
|
self.logger.info("🗃️ Found %d unfinished uploads", len(unfinished))
|
66
86
|
for file_version in unfinished:
|
67
|
-
|
87
|
+
# Use the correct attribute names for UnfinishedLargeFile objects
|
88
|
+
file_id = file_version.file_id
|
68
89
|
file_name = file_version.file_name
|
69
90
|
if self.dry_run:
|
70
91
|
self.logger.info(f"💡 Dry run: would cancel {file_id} ({file_name})")
|
71
92
|
else:
|
72
93
|
self.logger.info(f"🗑️ Cancelling {file_id} ({file_name})")
|
73
|
-
self.api.cancel_large_file(file_id)
|
74
|
-
|
75
|
-
|
76
|
-
@click.command()
|
77
|
-
@click.argument("bucket")
|
78
|
-
@click.option(
|
79
|
-
"--dry-run", is_flag=True, help="Only list unfinished uploads, do not cancel."
|
80
|
-
)
|
81
|
-
@click.option("--log-file", default="b2_cleanup.log", help="Path to log file.")
|
82
|
-
@click.option("--key-id", help="Backblaze B2 applicationKeyId to override env/config.")
|
83
|
-
@click.option("--key", help="Backblaze B2 applicationKey to override env/config.")
|
84
|
-
def cli(bucket, dry_run, log_file, key_id, key):
|
85
|
-
"""Clean up unfinished large file uploads in BUCKET."""
|
86
|
-
logger = logging.getLogger()
|
87
|
-
logger.setLevel(logging.INFO)
|
88
|
-
if logger.hasHandlers():
|
89
|
-
logger.handlers.clear()
|
90
|
-
|
91
|
-
file_handler = logging.FileHandler(log_file)
|
92
|
-
file_handler.setFormatter(
|
93
|
-
logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
|
94
|
-
)
|
95
|
-
logger.addHandler(file_handler)
|
96
|
-
|
97
|
-
stream_handler = logging.StreamHandler()
|
98
|
-
stream_handler.setFormatter(logging.Formatter("%(message)s"))
|
99
|
-
logger.addHandler(stream_handler)
|
100
|
-
|
101
|
-
tool = B2CleanupTool(dry_run=dry_run, override_key_id=key_id, override_key=key)
|
102
|
-
tool.cleanup_unfinished_uploads(bucket)
|
103
|
-
|
104
|
-
|
105
|
-
if __name__ == "__main__":
|
106
|
-
cli()
|
94
|
+
self.api.cancel_large_file(file_id)
|
@@ -0,0 +1,196 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: b2-cleanup
|
3
|
+
Version: 0.1.4
|
4
|
+
Summary: CLI tool to clean up unfinished Backblaze B2 large uploads
|
5
|
+
Project-URL: Homepage, https://github.com/your-username/b2-cleanup
|
6
|
+
Project-URL: Issues, https://github.com/your-username/b2-cleanup/issues
|
7
|
+
Project-URL: Changelog, https://github.com/your-username/b2-cleanup/blob/main/CHANGELOG.md
|
8
|
+
Author-email: Jeroen Verhoeven <jeroen@joentje.org>
|
9
|
+
License: MIT
|
10
|
+
Keywords: b2,backblaze,cleanup,cli,cloud-storage
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
12
|
+
Classifier: Intended Audience :: Developers
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
19
|
+
Classifier: Topic :: Utilities
|
20
|
+
Requires-Python: >=3.8
|
21
|
+
Requires-Dist: b2sdk>=1.20.0
|
22
|
+
Requires-Dist: click>=8.0
|
23
|
+
Provides-Extra: dev
|
24
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
25
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == 'dev'
|
26
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
27
|
+
Description-Content-Type: text/markdown
|
28
|
+
|
29
|
+
# B2 Cleanup
|
30
|
+
|
31
|
+
A CLI tool and Python library to clean up unfinished Backblaze B2 large file uploads.
|
32
|
+
|
33
|
+
[](https://badge.fury.io/py/b2-cleanup)
|
34
|
+
[](https://pypi.org/project/b2-cleanup/)
|
35
|
+
|
36
|
+
## 📋 Overview
|
37
|
+
|
38
|
+
When uploading large files to Backblaze B2, interrupted uploads can leave behind unfinished file parts that consume storage and incur costs. This tool helps you identify and clean up these unfinished uploads.
|
39
|
+
|
40
|
+
---
|
41
|
+
|
42
|
+
## 🔧 Features
|
43
|
+
|
44
|
+
- Lists all unfinished large file uploads in a given B2 bucket
|
45
|
+
- Optionally cancels them (dry-run support included)
|
46
|
+
- Uses the official `b2sdk` for native Backblaze API access
|
47
|
+
- Supports authentication via env vars, CLI override, or the `b2` CLI
|
48
|
+
- Clean CLI with logging support
|
49
|
+
- Class-based and easily extensible
|
50
|
+
|
51
|
+
---
|
52
|
+
|
53
|
+
## 🚀 Installation
|
54
|
+
|
55
|
+
```bash
|
56
|
+
pip install b2-cleanup
|
57
|
+
```
|
58
|
+
|
59
|
+
---
|
60
|
+
|
61
|
+
## 🧪 Usage
|
62
|
+
|
63
|
+
```bash
|
64
|
+
# Basic usage (requires B2 CLI to be installed and authorized)
|
65
|
+
b2-cleanup your-bucket-name
|
66
|
+
```
|
67
|
+
|
68
|
+
```bash
|
69
|
+
# Use with explicit credentials
|
70
|
+
b2-cleanup your-bucket-name --key-id YOUR_KEY_ID --key YOUR_APPLICATION_KEY
|
71
|
+
```
|
72
|
+
|
73
|
+
```bash
|
74
|
+
# Dry run to preview what would be deleted
|
75
|
+
b2-cleanup your-bucket-name --dry-run
|
76
|
+
```
|
77
|
+
|
78
|
+
### Example (dry run):
|
79
|
+
|
80
|
+
```bash
|
81
|
+
b2-cleanup my-bucket --dry-run
|
82
|
+
```
|
83
|
+
|
84
|
+
### Example (delete for real, with logging):
|
85
|
+
|
86
|
+
```bash
|
87
|
+
b2-cleanup my-bucket --log-file cleanup_$(date +%F).log
|
88
|
+
```
|
89
|
+
|
90
|
+
### Example (override credentials):
|
91
|
+
|
92
|
+
```bash
|
93
|
+
b2-cleanup my-bucket --key-id my-key-id --key my-app-key
|
94
|
+
```
|
95
|
+
|
96
|
+
### Example (Python usage):
|
97
|
+
|
98
|
+
```python
|
99
|
+
from b2_cleanup import B2CleanupTool
|
100
|
+
|
101
|
+
# Using environment variables or B2 CLI for auth
|
102
|
+
tool = B2CleanupTool(dry_run=True)
|
103
|
+
|
104
|
+
# Using explicit credentials
|
105
|
+
tool = B2CleanupTool(
|
106
|
+
dry_run=False,
|
107
|
+
override_key_id="your-key-id",
|
108
|
+
override_key="your-application-key"
|
109
|
+
)
|
110
|
+
|
111
|
+
# Clean up unfinished uploads
|
112
|
+
tool.cleanup_unfinished_uploads("your-bucket-name")
|
113
|
+
```
|
114
|
+
|
115
|
+
---
|
116
|
+
|
117
|
+
## 🔐 Authentication
|
118
|
+
|
119
|
+
This tool supports three ways to authenticate with B2, in priority order:
|
120
|
+
|
121
|
+
1. **Explicit CLI arguments**:
|
122
|
+
```bash
|
123
|
+
b2-cleanup bucket-name --key-id YOUR_KEY_ID --key YOUR_APPLICATION_KEY
|
124
|
+
```
|
125
|
+
|
126
|
+
2. **Environment variables**:
|
127
|
+
```bash
|
128
|
+
export B2_APPLICATION_KEY_ID=abc123
|
129
|
+
export B2_APPLICATION_KEY=supersecretkey
|
130
|
+
b2-cleanup bucket-name
|
131
|
+
```
|
132
|
+
|
133
|
+
3. **The `b2` CLI** (must be previously authorized):
|
134
|
+
```bash
|
135
|
+
b2 account authorize
|
136
|
+
# Then the tool will read credentials via:
|
137
|
+
b2 account get
|
138
|
+
```
|
139
|
+
|
140
|
+
---
|
141
|
+
|
142
|
+
## 📁 Project Structure
|
143
|
+
|
144
|
+
```
|
145
|
+
b2-cleanup/
|
146
|
+
├── b2_cleanup/
|
147
|
+
│ ├── __init__.py # Package exports
|
148
|
+
│ ├── core.py # Core functionality
|
149
|
+
│ └── cli.py # CLI implementation
|
150
|
+
├── tests/
|
151
|
+
│ ├── __init__.py
|
152
|
+
│ ├── test_core.py
|
153
|
+
│ └── test_cli.py
|
154
|
+
├── pyproject.toml # Project metadata + dependencies
|
155
|
+
├── CHANGELOG.md # Version history
|
156
|
+
├── .gitignore
|
157
|
+
└── README.md
|
158
|
+
```
|
159
|
+
|
160
|
+
---
|
161
|
+
|
162
|
+
## 📦 Packaging Notes
|
163
|
+
|
164
|
+
- The CLI entry point is `b2-cleanup` via `pyproject.toml`
|
165
|
+
- Install in editable mode (`uv pip install -e .`) for fast development
|
166
|
+
- Dependencies are managed via [`uv`](https://github.com/astral-sh/uv)
|
167
|
+
- Testing dependencies: `uv pip install -e ".[dev]"`
|
168
|
+
|
169
|
+
---
|
170
|
+
|
171
|
+
## 🧪 Testing
|
172
|
+
|
173
|
+
```bash
|
174
|
+
# Install dev dependencies
|
175
|
+
pip install b2-cleanup[dev]
|
176
|
+
|
177
|
+
# Run tests
|
178
|
+
pytest
|
179
|
+
|
180
|
+
# With coverage
|
181
|
+
pytest --cov=b2_cleanup
|
182
|
+
```
|
183
|
+
|
184
|
+
## 🛠️ Roadmap
|
185
|
+
|
186
|
+
- [ ] Filter uploads by file age
|
187
|
+
- [ ] Support multiple buckets
|
188
|
+
- [ ] Output metrics (count, size, cost saved)
|
189
|
+
- [ ] Optional integration with S3-compatible B2 APIs
|
190
|
+
|
191
|
+
---
|
192
|
+
|
193
|
+
## 📝 License
|
194
|
+
|
195
|
+
MIT License © 2025 Jeroen Verhoeven
|
196
|
+
|
@@ -0,0 +1,7 @@
|
|
1
|
+
b2_cleanup/__init__.py,sha256=2AjGt1V_1mxLhPRvAah_m6T7yWa-TnxdfKZpDoS1pVM,155
|
2
|
+
b2_cleanup/cli.py,sha256=gSBPzlP-rDDuiGqXvpgPD8VH_CNFlvel_TzPyHAPafQ,1273
|
3
|
+
b2_cleanup/core.py,sha256=usnBc6ugEVOX7w2b8b35jAUTYSwSi2kzZdAPq2JfR4U,3687
|
4
|
+
b2_cleanup-0.1.4.dist-info/METADATA,sha256=6k0DcgOv-BzO7o3YT1G5v27clFdPZbC5ZjyFVPGBWlw,4853
|
5
|
+
b2_cleanup-0.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
6
|
+
b2_cleanup-0.1.4.dist-info/entry_points.txt,sha256=zrE0HFNfpIaNCY44wha_k1u4dNAbUt_YrBrRVU2j2_4,50
|
7
|
+
b2_cleanup-0.1.4.dist-info/RECORD,,
|
@@ -1,6 +0,0 @@
|
|
1
|
-
cleanup_unfinished_b2_uploads.py,sha256=_wShubS02qNxiRdJmuNSoLzcjd7epcB3Q_fU1CDpH_Q,3877
|
2
|
-
b2_cleanup-0.1.0.dist-info/METADATA,sha256=kHW0eHyzVtwFoYT9WNbG5g2HKJ6bESQ7YnXD4wLN1XY,215
|
3
|
-
b2_cleanup-0.1.0.dist-info/WHEEL,sha256=ooBFpIzZCPdw3uqIQsOo4qqbA4ZRPxHnOH7peeONza0,91
|
4
|
-
b2_cleanup-0.1.0.dist-info/entry_points.txt,sha256=hRMR8HC39r-P1TrTPYbH3hDQkBVvDb508tOUS8a0CcI,65
|
5
|
-
b2_cleanup-0.1.0.dist-info/top_level.txt,sha256=GmKi68dORi-iv2h2sT5Uf7Q3udlX1mElQQg50cqqF6o,30
|
6
|
-
b2_cleanup-0.1.0.dist-info/RECORD,,
|
@@ -1 +0,0 @@
|
|
1
|
-
cleanup_unfinished_b2_uploads
|