b2-cleanup 0.1.0__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.
@@ -0,0 +1,7 @@
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
@@ -0,0 +1,99 @@
1
+ # b2-cleanup
2
+
3
+ ๐Ÿงน A Python CLI tool to clean up **unfinished large file uploads** in a [Backblaze B2](https://www.backblaze.com/b2/cloud-storage.html) bucket.
4
+
5
+ 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.
6
+
7
+ ---
8
+
9
+ ## ๐Ÿ”ง Features
10
+
11
+ - Lists all unfinished large file uploads in a given B2 bucket
12
+ - Optionally cancels them (dry-run support included)
13
+ - Uses the official `b2sdk` for native Backblaze API access
14
+ - Clean CLI with logging support
15
+ - Class-based and easily extensible
16
+
17
+ ---
18
+
19
+ ## ๐Ÿš€ Installation
20
+
21
+ ### 1. Clone and create an isolated environment
22
+
23
+ ```bash
24
+ git clone https://github.com/<your-username>/b2-cleanup.git
25
+ cd b2-cleanup
26
+
27
+ uv venv
28
+ source .venv/bin/activate
29
+ uv pip install -e .
30
+ ```
31
+
32
+ > Requires [uv](https://github.com/astral-sh/uv) and Python 3.8+
33
+
34
+ ---
35
+
36
+ ## ๐Ÿงช Usage
37
+
38
+ ```bash
39
+ b2-cleanup BUCKET_NAME [OPTIONS]
40
+ ```
41
+
42
+ ### Example (dry run):
43
+
44
+ ```bash
45
+ b2-cleanup my-bucket --dry-run
46
+ ```
47
+
48
+ ### Example (delete for real, with logging):
49
+
50
+ ```bash
51
+ b2-cleanup my-bucket --log-file cleanup_$(date +%F).log
52
+ ```
53
+
54
+ ---
55
+
56
+ ## ๐Ÿ” Authentication
57
+
58
+ You must be logged in with the `b2` CLI at least once:
59
+
60
+ ```bash
61
+ b2 authorize-account
62
+ ```
63
+
64
+ This stores credentials in `~/.b2_account_info`, which the tool reuses.
65
+
66
+ ---
67
+
68
+ ## ๐Ÿ“ Project Structure
69
+
70
+ ```
71
+ b2-cleanup/
72
+ โ”œโ”€โ”€ cleanup_unfinished_b2_uploads.py # Core CLI logic (class-based)
73
+ โ”œโ”€โ”€ pyproject.toml # Project metadata + dependencies
74
+ โ”œโ”€โ”€ .gitignore
75
+ โ””โ”€โ”€ README.md
76
+ ```
77
+
78
+ ---
79
+
80
+ ## ๐Ÿ“ฆ Packaging Notes
81
+
82
+ - The CLI entry point is `b2-cleanup` via `pyproject.toml`
83
+ - Install in editable mode (`uv pip install -e .`) for fast development
84
+ - Dependencies are managed via [`uv`](https://github.com/astral-sh/uv) for reproducibility and speed
85
+
86
+ ---
87
+
88
+ ## ๐Ÿ› ๏ธ Roadmap
89
+
90
+ - [ ] Filter uploads by file age
91
+ - [ ] Support multiple buckets
92
+ - [ ] Output metrics (count, size, cost saved)
93
+ - [ ] Optional integration with S3-compatible B2 APIs
94
+
95
+ ---
96
+
97
+ ## ๐Ÿ“ License
98
+
99
+ MIT License ยฉ 2025 Your Name
@@ -0,0 +1,7 @@
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
@@ -0,0 +1,9 @@
1
+ README.md
2
+ cleanup_unfinished_b2_uploads.py
3
+ pyproject.toml
4
+ b2_cleanup.egg-info/PKG-INFO
5
+ b2_cleanup.egg-info/SOURCES.txt
6
+ b2_cleanup.egg-info/dependency_links.txt
7
+ b2_cleanup.egg-info/entry_points.txt
8
+ b2_cleanup.egg-info/requires.txt
9
+ b2_cleanup.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ b2-cleanup = cleanup_unfinished_b2_uploads:cli
@@ -0,0 +1,2 @@
1
+ click>=8.0
2
+ b2sdk>=2.8.1
@@ -0,0 +1 @@
1
+ cleanup_unfinished_b2_uploads
@@ -0,0 +1,106 @@
1
+ import os
2
+ import json
3
+ import subprocess
4
+ import logging
5
+ import click
6
+ from b2sdk.v2 import InMemoryAccountInfo, B2Api
7
+
8
+
9
+ class B2CleanupTool:
10
+ def __init__(
11
+ self,
12
+ dry_run: bool = False,
13
+ override_key_id: str = None,
14
+ override_key: str = None,
15
+ ):
16
+ self.dry_run = dry_run
17
+ self.logger = logging.getLogger("B2Cleanup")
18
+ self.api = self._authorize(override_key_id, override_key)
19
+
20
+ def _authorize(self, override_key_id=None, override_key=None):
21
+ info = InMemoryAccountInfo()
22
+ api = B2Api(info)
23
+
24
+ if override_key_id and override_key:
25
+ self.logger.info("๐Ÿ” Using credentials from CLI override.")
26
+ api.authorize_account("production", override_key_id, override_key)
27
+ return api
28
+
29
+ key_id = os.getenv("B2_APPLICATION_KEY_ID")
30
+ app_key = os.getenv("B2_APPLICATION_KEY")
31
+
32
+ if key_id and app_key:
33
+ self.logger.info("๐Ÿ” Using credentials from environment variables.")
34
+ api.authorize_account("production", key_id, app_key)
35
+ return api
36
+
37
+ try:
38
+ 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
+ )
45
+ creds = json.loads(result.stdout)
46
+ key_id = creds["applicationKeyId"]
47
+ app_key = creds["applicationKey"]
48
+ api.authorize_account("production", key_id, app_key)
49
+ self.logger.info("โœ… Authorized with B2 CLI credentials.")
50
+ return api
51
+
52
+ except (subprocess.CalledProcessError, KeyError, json.JSONDecodeError) as e:
53
+ self.logger.error(
54
+ "โŒ Failed to get B2 credentials from CLI or environment: %s", e
55
+ )
56
+ raise RuntimeError("Could not authorize with Backblaze B2.")
57
+
58
+ def cleanup_unfinished_uploads(self, bucket_name: str):
59
+ bucket = self.api.get_bucket_by_name(bucket_name)
60
+ unfinished = list(bucket.list_unfinished_large_files())
61
+ if not unfinished:
62
+ self.logger.info("โœ… No unfinished large files found.")
63
+ return
64
+
65
+ self.logger.info("๐Ÿ—ƒ๏ธ Found %d unfinished uploads", len(unfinished))
66
+ for file_version in unfinished:
67
+ file_id = file_version.id_
68
+ file_name = file_version.file_name
69
+ if self.dry_run:
70
+ self.logger.info(f"๐Ÿ’ก Dry run: would cancel {file_id} ({file_name})")
71
+ else:
72
+ 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()
@@ -0,0 +1,15 @@
1
+ [project]
2
+ name = "b2-cleanup"
3
+ version = "0.1.0"
4
+ description = "Cleanup unfinished Backblaze B2 large uploads"
5
+ authors = [
6
+ { name = "Jeroen Verhoeven", email = "jeroen@joentje.org" }
7
+ ]
8
+ dependencies = [
9
+ "click>=8.0",
10
+ "b2sdk>=2.8.1"
11
+ ]
12
+
13
+ [project.scripts]
14
+ b2-cleanup = "cleanup_unfinished_b2_uploads:cli"
15
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+