openbucket-client 0.1.1__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.
openbucket/__init__.py ADDED
@@ -0,0 +1,58 @@
1
+ """Typed Python client for the OpenBucket management API."""
2
+
3
+ from .client import OpenBucketClient
4
+ from .exceptions import (
5
+ OpenBucketConfigurationError,
6
+ OpenBucketConnectionError,
7
+ OpenBucketError,
8
+ OpenBucketHTTPError,
9
+ OpenBucketProtocolError,
10
+ )
11
+ from .models import (
12
+ AccessKey,
13
+ Analytics,
14
+ AnalyticsStorage,
15
+ Bucket,
16
+ ClientConfiguration,
17
+ DailyAnalytics,
18
+ DeleteResult,
19
+ Endpoints,
20
+ Health,
21
+ NodeInfo,
22
+ ObjectHead,
23
+ ObjectInfo,
24
+ RequestLog,
25
+ Share,
26
+ Status,
27
+ StopResult,
28
+ StorageInfo,
29
+ )
30
+
31
+ __version__ = "0.1.1"
32
+
33
+ __all__ = [
34
+ "AccessKey",
35
+ "Analytics",
36
+ "AnalyticsStorage",
37
+ "Bucket",
38
+ "ClientConfiguration",
39
+ "DailyAnalytics",
40
+ "DeleteResult",
41
+ "Endpoints",
42
+ "Health",
43
+ "NodeInfo",
44
+ "ObjectHead",
45
+ "ObjectInfo",
46
+ "OpenBucketClient",
47
+ "OpenBucketConfigurationError",
48
+ "OpenBucketConnectionError",
49
+ "OpenBucketError",
50
+ "OpenBucketHTTPError",
51
+ "OpenBucketProtocolError",
52
+ "RequestLog",
53
+ "Share",
54
+ "Status",
55
+ "StopResult",
56
+ "StorageInfo",
57
+ "__version__",
58
+ ]
openbucket/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
openbucket/cli.py ADDED
@@ -0,0 +1,204 @@
1
+ """Command-line interface for the Python management client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import mimetypes
8
+ import os
9
+ import sys
10
+ from collections.abc import Mapping, Sequence
11
+ from dataclasses import asdict, is_dataclass
12
+ from datetime import date, datetime
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ from . import __version__
17
+ from .client import OpenBucketClient
18
+ from .exceptions import OpenBucketError, OpenBucketHTTPError
19
+
20
+
21
+ def _parser() -> argparse.ArgumentParser:
22
+ parser = argparse.ArgumentParser(
23
+ prog="openbucket-client",
24
+ description="Manage a running OpenBucket daemon through its HTTP API.",
25
+ )
26
+ parser.add_argument(
27
+ "--url",
28
+ default=os.environ.get("OPENBUCKET_API_URL", "http://127.0.0.1:7272"),
29
+ help="management API URL (default: OPENBUCKET_API_URL or http://127.0.0.1:7272)",
30
+ )
31
+ parser.add_argument(
32
+ "--token",
33
+ default=os.environ.get("OPENBUCKET_ADMIN_TOKEN"),
34
+ help="management bearer token (prefer OPENBUCKET_ADMIN_TOKEN to avoid process-list exposure)",
35
+ )
36
+ parser.add_argument("--timeout", type=float, default=30.0, help="request timeout in seconds")
37
+ parser.add_argument("--json", action="store_true", help="emit machine-readable JSON")
38
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
39
+
40
+ commands = parser.add_subparsers(dest="command", required=True)
41
+ commands.add_parser("health", help="check the unauthenticated daemon health endpoint")
42
+ commands.add_parser("status", help="show daemon and storage status")
43
+ commands.add_parser("config", help="show connection configuration")
44
+ commands.add_parser("analytics", help="show aggregate request and storage analytics")
45
+
46
+ logs = commands.add_parser("logs", help="show newest request logs")
47
+ logs.add_argument("--limit", type=int, default=100)
48
+
49
+ buckets = commands.add_parser("buckets", help="manage buckets").add_subparsers(dest="bucket_command", required=True)
50
+ buckets.add_parser("list", help="list buckets")
51
+ create_bucket = buckets.add_parser("create", help="create a bucket")
52
+ create_bucket.add_argument("name")
53
+ create_bucket.add_argument("--public", action="store_true")
54
+ update_bucket = buckets.add_parser("set-public", help="change anonymous-read visibility")
55
+ update_bucket.add_argument("name")
56
+ visibility = update_bucket.add_mutually_exclusive_group(required=True)
57
+ visibility.add_argument("--public", dest="public", action="store_true")
58
+ visibility.add_argument("--private", dest="public", action="store_false")
59
+ delete_bucket = buckets.add_parser("delete", help="delete a bucket")
60
+ delete_bucket.add_argument("name")
61
+ delete_bucket.add_argument("--force", action="store_true")
62
+
63
+ objects = commands.add_parser("objects", help="manage objects").add_subparsers(dest="object_command", required=True)
64
+ list_objects = objects.add_parser("list", help="list objects")
65
+ list_objects.add_argument("bucket")
66
+ list_objects.add_argument("--prefix", default="")
67
+ upload = objects.add_parser("upload", help="upload a local file")
68
+ upload.add_argument("bucket")
69
+ upload.add_argument("key")
70
+ upload.add_argument("file", type=Path)
71
+ upload.add_argument("--content-type")
72
+ download = objects.add_parser("download", help="download to a local file")
73
+ download.add_argument("bucket")
74
+ download.add_argument("key")
75
+ download.add_argument("output", type=Path)
76
+ download.add_argument("--force", action="store_true", help="replace an existing output file")
77
+ head = objects.add_parser("head", help="inspect object metadata")
78
+ head.add_argument("bucket")
79
+ head.add_argument("key")
80
+ delete_object = objects.add_parser("delete", help="delete an object")
81
+ delete_object.add_argument("bucket")
82
+ delete_object.add_argument("key")
83
+
84
+ keys = commands.add_parser("keys", help="manage S3 access keys").add_subparsers(dest="key_command", required=True)
85
+ keys.add_parser("list", help="list keys without secrets")
86
+ create_key = keys.add_parser("create", help="create a key; the secret is returned once")
87
+ create_key.add_argument("--name", default="access key")
88
+ create_key.add_argument("--read-only", action="store_true")
89
+ create_key.add_argument("--bucket")
90
+ revoke = keys.add_parser("revoke", help="revoke a key")
91
+ revoke.add_argument("id")
92
+
93
+ share = commands.add_parser("share", help="create an expiring object URL")
94
+ share.add_argument("bucket")
95
+ share.add_argument("key")
96
+ share.add_argument("--expires-in", type=int, default=3600)
97
+
98
+ return parser
99
+
100
+
101
+ def _jsonable(value: Any) -> Any:
102
+ if is_dataclass(value) and not isinstance(value, type):
103
+ return _jsonable(asdict(value))
104
+ if isinstance(value, datetime):
105
+ text = value.isoformat()
106
+ return text.replace("+00:00", "Z")
107
+ if isinstance(value, date):
108
+ return value.isoformat()
109
+ if isinstance(value, Mapping):
110
+ return {str(key): _jsonable(item) for key, item in value.items()}
111
+ if isinstance(value, (list, tuple)):
112
+ return [_jsonable(item) for item in value]
113
+ return value
114
+
115
+
116
+ def _emit(value: Any, *, as_json: bool) -> None:
117
+ converted = _jsonable(value)
118
+ if as_json:
119
+ print(json.dumps(converted, ensure_ascii=False, separators=(",", ":")))
120
+ return
121
+ if isinstance(converted, list):
122
+ if not converted:
123
+ print("No results.")
124
+ return
125
+ for item in converted:
126
+ print(json.dumps(item, ensure_ascii=False, sort_keys=True))
127
+ return
128
+ if isinstance(converted, dict):
129
+ for key, item in converted.items():
130
+ if isinstance(item, (dict, list)):
131
+ print(f"{key}: {json.dumps(item, ensure_ascii=False, sort_keys=True)}")
132
+ else:
133
+ print(f"{key}: {item}")
134
+ return
135
+ print(converted)
136
+
137
+
138
+ def _run(client: OpenBucketClient, args: argparse.Namespace) -> Any:
139
+ if args.command == "health":
140
+ return client.health()
141
+ if args.command == "status":
142
+ return client.status()
143
+ if args.command == "config":
144
+ return client.client_configuration()
145
+ if args.command == "analytics":
146
+ return client.analytics()
147
+ if args.command == "logs":
148
+ return client.logs(limit=args.limit)
149
+ if args.command == "buckets":
150
+ if args.bucket_command == "list":
151
+ return client.list_buckets()
152
+ if args.bucket_command == "create":
153
+ return client.create_bucket(args.name, public=args.public)
154
+ if args.bucket_command == "set-public":
155
+ return client.set_bucket_public(args.name, args.public)
156
+ if args.bucket_command == "delete":
157
+ return client.delete_bucket(args.name, force=args.force)
158
+ if args.command == "objects":
159
+ if args.object_command == "list":
160
+ return client.list_objects(args.bucket, prefix=args.prefix)
161
+ if args.object_command == "upload":
162
+ content_type = args.content_type or mimetypes.guess_type(args.file.name)[0] or "application/octet-stream"
163
+ return client.upload_file(args.bucket, args.key, args.file, content_type=content_type)
164
+ if args.object_command == "download":
165
+ head = client.download_to(args.bucket, args.key, args.output, overwrite=args.force)
166
+ return {"output": str(args.output), "object": head}
167
+ if args.object_command == "head":
168
+ return client.head_object(args.bucket, args.key)
169
+ if args.object_command == "delete":
170
+ return client.delete_object(args.bucket, args.key)
171
+ if args.command == "keys":
172
+ if args.key_command == "list":
173
+ return client.list_keys()
174
+ if args.key_command == "create":
175
+ return client.create_key(name=args.name, read_only=args.read_only, bucket=args.bucket)
176
+ if args.key_command == "revoke":
177
+ return client.revoke_key(args.id)
178
+ if args.command == "share":
179
+ return client.create_share(args.bucket, args.key, expires_in=args.expires_in)
180
+ raise RuntimeError("unreachable command")
181
+
182
+
183
+ def main(argv: Sequence[str] | None = None) -> int:
184
+ parser = _parser()
185
+ args = parser.parse_args(argv)
186
+ if not args.token:
187
+ parser.error("a management token is required; set OPENBUCKET_ADMIN_TOKEN or pass --token")
188
+ try:
189
+ client = OpenBucketClient(args.url, args.token, timeout=args.timeout)
190
+ result = _run(client, args)
191
+ _emit(result, as_json=args.json)
192
+ return 0
193
+ except OpenBucketHTTPError as error:
194
+ print(str(error), file=sys.stderr)
195
+ if error.details is not None:
196
+ print(f"details: {json.dumps(error.details, ensure_ascii=False)}", file=sys.stderr)
197
+ return 1
198
+ except (OpenBucketError, OSError, ValueError) as error:
199
+ print(f"openbucket-client: {error}", file=sys.stderr)
200
+ return 1
201
+
202
+
203
+ if __name__ == "__main__":
204
+ raise SystemExit(main())