b2-cleanup 0.1.3__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)
@@ -1,31 +1,41 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: b2-cleanup
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: CLI tool to clean up unfinished Backblaze B2 large uploads
5
- Author-email: Jeroen Verhoeven <jeroen@joentje.org>
6
- License: MIT
7
5
  Project-URL: Homepage, https://github.com/your-username/b2-cleanup
8
6
  Project-URL: Issues, https://github.com/your-username/b2-cleanup/issues
9
- Keywords: backblaze,b2,cli,cloud-storage,cleanup
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
10
11
  Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
11
13
  Classifier: License :: OSI Approved :: MIT License
12
14
  Classifier: Programming Language :: Python :: 3
13
15
  Classifier: Programming Language :: Python :: 3.8
14
16
  Classifier: Programming Language :: Python :: 3.9
15
17
  Classifier: Programming Language :: Python :: 3.10
16
18
  Classifier: Programming Language :: Python :: 3.11
17
- Classifier: Intended Audience :: Developers
18
19
  Classifier: Topic :: Utilities
19
20
  Requires-Python: >=3.8
20
- Description-Content-Type: text/markdown
21
- Requires-Dist: click>=8.0
22
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
23
30
 
24
- # b2-cleanup
31
+ A CLI tool and Python library to clean up unfinished Backblaze B2 large file uploads.
25
32
 
26
- 🧹 A Python CLI tool to clean up **unfinished large file uploads** in a [Backblaze B2](https://www.backblaze.com/b2/cloud-storage.html) bucket.
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/)
27
35
 
28
- Built using [`b2sdk`](https://github.com/Backblaze/b2-sdk-python), [`click`](https://click.palletsprojects.com/), and [`uv`](https://github.com/astral-sh/uv) for performance and reproducibility.
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.
29
39
 
30
40
  ---
31
41
 
@@ -42,25 +52,27 @@ Built using [`b2sdk`](https://github.com/Backblaze/b2-sdk-python), [`click`](htt
42
52
 
43
53
  ## 🚀 Installation
44
54
 
45
- ### 1. Clone and create an isolated environment
46
-
47
55
  ```bash
48
- git clone https://github.com/<your-username>/b2-cleanup.git
49
- cd b2-cleanup
50
-
51
- uv venv
52
- source .venv/bin/activate
53
- uv pip install -e .
56
+ pip install b2-cleanup
54
57
  ```
55
58
 
56
- > Requires [uv](https://github.com/astral-sh/uv) and Python 3.8+
57
-
58
59
  ---
59
60
 
60
61
  ## 🧪 Usage
61
62
 
62
63
  ```bash
63
- b2-cleanup BUCKET_NAME [OPTIONS]
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
64
76
  ```
65
77
 
66
78
  ### Example (dry run):
@@ -81,6 +93,25 @@ b2-cleanup my-bucket --log-file cleanup_$(date +%F).log
81
93
  b2-cleanup my-bucket --key-id my-key-id --key my-app-key
82
94
  ```
83
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
+
84
115
  ---
85
116
 
86
117
  ## 🔐 Authentication
@@ -89,20 +120,21 @@ This tool supports three ways to authenticate with B2, in priority order:
89
120
 
90
121
  1. **Explicit CLI arguments**:
91
122
  ```bash
92
- b2-cleanup my-bucket --key-id abc123 --key supersecretkey
123
+ b2-cleanup bucket-name --key-id YOUR_KEY_ID --key YOUR_APPLICATION_KEY
93
124
  ```
94
125
 
95
126
  2. **Environment variables**:
96
127
  ```bash
97
128
  export B2_APPLICATION_KEY_ID=abc123
98
129
  export B2_APPLICATION_KEY=supersecretkey
130
+ b2-cleanup bucket-name
99
131
  ```
100
132
 
101
133
  3. **The `b2` CLI** (must be previously authorized):
102
134
  ```bash
103
135
  b2 account authorize
104
136
  # Then the tool will read credentials via:
105
- b2 account info
137
+ b2 account get
106
138
  ```
107
139
 
108
140
  ---
@@ -111,8 +143,16 @@ This tool supports three ways to authenticate with B2, in priority order:
111
143
 
112
144
  ```
113
145
  b2-cleanup/
114
- ├── cleanup_unfinished_b2_uploads.py # Core CLI logic (class-based)
115
- ├── pyproject.toml # Project metadata + dependencies
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
116
156
  ├── .gitignore
117
157
  └── README.md
118
158
  ```
@@ -124,9 +164,23 @@ b2-cleanup/
124
164
  - The CLI entry point is `b2-cleanup` via `pyproject.toml`
125
165
  - Install in editable mode (`uv pip install -e .`) for fast development
126
166
  - Dependencies are managed via [`uv`](https://github.com/astral-sh/uv)
167
+ - Testing dependencies: `uv pip install -e ".[dev]"`
127
168
 
128
169
  ---
129
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
+
130
184
  ## 🛠️ Roadmap
131
185
 
132
186
  - [ ] Filter uploads by file age
@@ -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.3.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,6 +0,0 @@
1
- cleanup_unfinished_b2_uploads.py,sha256=_wShubS02qNxiRdJmuNSoLzcjd7epcB3Q_fU1CDpH_Q,3877
2
- b2_cleanup-0.1.3.dist-info/METADATA,sha256=Q4FYPDetwgRQE8GEXZw4H0H0gdX6oKnZakGieaFtWOw,3490
3
- b2_cleanup-0.1.3.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
4
- b2_cleanup-0.1.3.dist-info/entry_points.txt,sha256=hRMR8HC39r-P1TrTPYbH3hDQkBVvDb508tOUS8a0CcI,65
5
- b2_cleanup-0.1.3.dist-info/top_level.txt,sha256=GmKi68dORi-iv2h2sT5Uf7Q3udlX1mElQQg50cqqF6o,30
6
- b2_cleanup-0.1.3.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