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 ADDED
@@ -0,0 +1,6 @@
1
+ """B2 Cleanup Tool - Clean up unfinished Backblaze B2 large uploads."""
2
+
3
+ from .core import B2CleanupTool
4
+
5
+ __version__ = "0.1.3"
6
+ __all__ = ["B2CleanupTool"]
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
- result = subprocess.run(
40
- ["b2", "account", "get"],
41
- check=True,
42
- capture_output=True,
43
- text=True,
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
- file_id = file_version.id_
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
+ [![PyPI version](https://badge.fury.io/py/b2-cleanup.svg)](https://badge.fury.io/py/b2-cleanup)
34
+ [![Python Versions](https://img.shields.io/pypi/pyversions/b2-cleanup.svg)](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,5 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.0.1)
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
-
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ b2-cleanup = b2_cleanup.cli:cli
@@ -1,7 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: b2-cleanup
3
- Version: 0.1.0
4
- Summary: Cleanup unfinished Backblaze B2 large uploads
5
- Author-email: Jeroen Verhoeven <jeroen@joentje.org>
6
- Requires-Dist: click>=8.0
7
- Requires-Dist: b2sdk>=2.8.1
@@ -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,2 +0,0 @@
1
- [console_scripts]
2
- b2-cleanup = cleanup_unfinished_b2_uploads:cli
@@ -1 +0,0 @@
1
- cleanup_unfinished_b2_uploads