nexium-storage 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,88 @@
1
+ Metadata-Version: 2.4
2
+ Name: nexium-storage
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for NEXIUM Storage
5
+ Author-email: NEXIUM <ai.nexium@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://nexiumai.io
8
+ Project-URL: Documentation, https://console.nexiumai.io/docs
9
+ Keywords: nexium,storage,s3,r2,file-upload,cloud
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Internet :: WWW/HTTP
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ Provides-Extra: requests
23
+ Requires-Dist: requests>=2.28; extra == "requests"
24
+
25
+ # nexium-storage
26
+
27
+ Official Python SDK for [NEXIUM Storage](https://nexiumai.io) — upload, serve and manage files via a simple API.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ pip install nexium-storage
33
+
34
+ # For file upload support:
35
+ pip install "nexium-storage[requests]"
36
+ ```
37
+
38
+ ## Quick start
39
+
40
+ ```python
41
+ import os
42
+ from nexium_storage import NexiumStorage
43
+
44
+ storage = NexiumStorage(api_key=os.getenv("NEXIUM_API_KEY"))
45
+
46
+ # Upload
47
+ with open("photo.jpg", "rb") as f:
48
+ file = storage.upload(bucket_id, f, "photo.jpg", "image/jpeg")
49
+ print(file.url)
50
+
51
+ # List
52
+ result = storage.list(bucket_id, search="photo")
53
+ for f in result.files:
54
+ print(f.filename, f.size_bytes)
55
+
56
+ # Download URL
57
+ url = storage.download(file.id)
58
+
59
+ # Rename
60
+ storage.rename(file.id, "new-name.jpg")
61
+
62
+ # Delete
63
+ storage.delete(file.id)
64
+ ```
65
+
66
+ ## Webhook verification
67
+
68
+ ```python
69
+ from nexium_storage import NexiumStorage, NexiumError
70
+
71
+ # Flask example
72
+ @app.route("/webhook", methods=["POST"])
73
+ def webhook():
74
+ try:
75
+ payload = NexiumStorage.verify_webhook(
76
+ request.get_data(),
77
+ request.headers.get("X-Nexium-Signature"),
78
+ os.getenv("NEXIUM_WEBHOOK_SECRET"),
79
+ )
80
+ print(payload["event"], payload["data"])
81
+ return "", 200
82
+ except NexiumError:
83
+ return "", 401
84
+ ```
85
+
86
+ ## License
87
+
88
+ MIT
@@ -0,0 +1,64 @@
1
+ # nexium-storage
2
+
3
+ Official Python SDK for [NEXIUM Storage](https://nexiumai.io) — upload, serve and manage files via a simple API.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install nexium-storage
9
+
10
+ # For file upload support:
11
+ pip install "nexium-storage[requests]"
12
+ ```
13
+
14
+ ## Quick start
15
+
16
+ ```python
17
+ import os
18
+ from nexium_storage import NexiumStorage
19
+
20
+ storage = NexiumStorage(api_key=os.getenv("NEXIUM_API_KEY"))
21
+
22
+ # Upload
23
+ with open("photo.jpg", "rb") as f:
24
+ file = storage.upload(bucket_id, f, "photo.jpg", "image/jpeg")
25
+ print(file.url)
26
+
27
+ # List
28
+ result = storage.list(bucket_id, search="photo")
29
+ for f in result.files:
30
+ print(f.filename, f.size_bytes)
31
+
32
+ # Download URL
33
+ url = storage.download(file.id)
34
+
35
+ # Rename
36
+ storage.rename(file.id, "new-name.jpg")
37
+
38
+ # Delete
39
+ storage.delete(file.id)
40
+ ```
41
+
42
+ ## Webhook verification
43
+
44
+ ```python
45
+ from nexium_storage import NexiumStorage, NexiumError
46
+
47
+ # Flask example
48
+ @app.route("/webhook", methods=["POST"])
49
+ def webhook():
50
+ try:
51
+ payload = NexiumStorage.verify_webhook(
52
+ request.get_data(),
53
+ request.headers.get("X-Nexium-Signature"),
54
+ os.getenv("NEXIUM_WEBHOOK_SECRET"),
55
+ )
56
+ print(payload["event"], payload["data"])
57
+ return "", 200
58
+ except NexiumError:
59
+ return "", 401
60
+ ```
61
+
62
+ ## License
63
+
64
+ MIT
@@ -0,0 +1,4 @@
1
+ from .client import NexiumStorage, NexiumError, StoredFile, FileList
2
+
3
+ __all__ = ["NexiumStorage", "NexiumError", "StoredFile", "FileList"]
4
+ __version__ = "0.1.0"
@@ -0,0 +1,169 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import hmac
5
+ import json
6
+ from dataclasses import dataclass
7
+ from typing import IO, Any, Callable, Optional
8
+ import urllib.request
9
+ import urllib.parse
10
+ import urllib.error
11
+
12
+ DEFAULT_BASE_URL = "https://api.nexiumai.io"
13
+
14
+
15
+ @dataclass
16
+ class StoredFile:
17
+ id: str
18
+ bucket_id: str
19
+ object_key: str
20
+ filename: str
21
+ mime_type: str
22
+ size_bytes: int
23
+ url: str
24
+ created_at: str
25
+
26
+ @classmethod
27
+ def from_dict(cls, d: dict) -> "StoredFile":
28
+ return cls(**{k: d[k] for k in cls.__dataclass_fields__})
29
+
30
+
31
+ @dataclass
32
+ class FileList:
33
+ files: list[StoredFile]
34
+ total: int
35
+ page: int
36
+ per_page: int
37
+
38
+ @classmethod
39
+ def from_dict(cls, d: dict) -> "FileList":
40
+ return cls(
41
+ files=[StoredFile.from_dict(f) for f in d.get("files", [])],
42
+ total=d["total"],
43
+ page=d["page"],
44
+ per_page=d["per_page"],
45
+ )
46
+
47
+
48
+ class NexiumError(Exception):
49
+ def __init__(self, status: int, message: str):
50
+ super().__init__(message)
51
+ self.status = status
52
+ self.message = message
53
+
54
+
55
+ class NexiumStorage:
56
+ """NEXIUM Storage Python SDK."""
57
+
58
+ def __init__(self, api_key: str, base_url: str = DEFAULT_BASE_URL):
59
+ if not api_key:
60
+ raise ValueError("api_key is required")
61
+ self.api_key = api_key
62
+ self.base_url = base_url.rstrip("/")
63
+
64
+ def _request(self, method: str, path: str, **kwargs) -> Any:
65
+ try:
66
+ import requests
67
+ json_body = kwargs.pop("json_body", None)
68
+ headers = {"Authorization": f"Bearer {self.api_key}"}
69
+ req_kwargs: dict = {"headers": headers, **kwargs}
70
+ if json_body is not None:
71
+ req_kwargs["json"] = json_body
72
+ resp = requests.request(method, f"{self.base_url}{path}", **req_kwargs)
73
+ if resp.status_code == 204:
74
+ return None
75
+ data = resp.json()
76
+ if not resp.ok:
77
+ raise NexiumError(resp.status_code, data.get("message", "Request failed"))
78
+ return data
79
+ except ImportError:
80
+ return self._request_stdlib(method, path, **kwargs)
81
+
82
+ def _request_stdlib(self, method: str, path: str, json_body=None, **_) -> Any:
83
+ url = f"{self.base_url}{path}"
84
+ body = json.dumps(json_body).encode() if json_body else None
85
+ headers = {
86
+ "Authorization": f"Bearer {self.api_key}",
87
+ "Content-Type": "application/json",
88
+ }
89
+ req = urllib.request.Request(url, data=body, headers=headers, method=method)
90
+ try:
91
+ with urllib.request.urlopen(req) as resp:
92
+ if resp.status == 204:
93
+ return None
94
+ return json.loads(resp.read())
95
+ except urllib.error.HTTPError as e:
96
+ data = json.loads(e.read())
97
+ raise NexiumError(e.code, data.get("message", "Request failed")) from e
98
+
99
+ # ─── Files ────────────────────────────────────────────────────────────────
100
+
101
+ def list(
102
+ self,
103
+ bucket_id: str,
104
+ search: str = "",
105
+ page: int = 1,
106
+ per_page: int = 24,
107
+ ) -> FileList:
108
+ """List files in a bucket."""
109
+ params: dict[str, Any] = {"page": page, "per_page": per_page}
110
+ if search:
111
+ params["search"] = search
112
+ qs = urllib.parse.urlencode(params)
113
+ data = self._request("GET", f"/api/v1/ext/buckets/{bucket_id}/files?{qs}")
114
+ return FileList.from_dict(data)
115
+
116
+ def upload(
117
+ self,
118
+ bucket_id: str,
119
+ file: IO[bytes],
120
+ filename: str,
121
+ mime_type: str = "application/octet-stream",
122
+ on_progress: Optional[Callable[[int], None]] = None,
123
+ ) -> StoredFile:
124
+ """Upload a file to a bucket. `file` can be any file-like object."""
125
+ try:
126
+ import requests
127
+ headers = {"Authorization": f"Bearer {self.api_key}"}
128
+ resp = requests.post(
129
+ f"{self.base_url}/api/v1/ext/buckets/{bucket_id}/files",
130
+ headers=headers,
131
+ files={"file": (filename, file, mime_type)},
132
+ )
133
+ data = resp.json()
134
+ if not resp.ok:
135
+ raise NexiumError(resp.status_code, data.get("message", "Upload failed"))
136
+ return StoredFile.from_dict(data)
137
+ except ImportError:
138
+ raise NexiumError(0, "Install `requests` for file upload: pip install requests")
139
+
140
+ def download(self, file_id: str) -> str:
141
+ """Get a temporary download URL for a file."""
142
+ data = self._request("GET", f"/api/v1/ext/files/{file_id}/download")
143
+ return data["url"]
144
+
145
+ def rename(self, file_id: str, filename: str) -> StoredFile:
146
+ """Rename a file."""
147
+ data = self._request("PATCH", f"/api/v1/ext/files/{file_id}", json_body={"filename": filename})
148
+ return StoredFile.from_dict(data)
149
+
150
+ def delete(self, file_id: str) -> None:
151
+ """Delete a file."""
152
+ self._request("DELETE", f"/api/v1/ext/files/{file_id}")
153
+
154
+ # ─── Webhooks ─────────────────────────────────────────────────────────────
155
+
156
+ @staticmethod
157
+ def verify_webhook(raw_body: bytes | str, signature: str, secret: str) -> Any:
158
+ """Verify a webhook signature and return the parsed payload.
159
+
160
+ Raises NexiumError if the signature is invalid.
161
+ """
162
+ if isinstance(raw_body, str):
163
+ raw_body = raw_body.encode()
164
+ expected = "sha256=" + hmac.new(
165
+ secret.encode(), raw_body, hashlib.sha256
166
+ ).hexdigest()
167
+ if not hmac.compare_digest(expected, signature):
168
+ raise NexiumError(401, "Invalid webhook signature")
169
+ return json.loads(raw_body)
@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.4
2
+ Name: nexium-storage
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for NEXIUM Storage
5
+ Author-email: NEXIUM <ai.nexium@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://nexiumai.io
8
+ Project-URL: Documentation, https://console.nexiumai.io/docs
9
+ Keywords: nexium,storage,s3,r2,file-upload,cloud
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Internet :: WWW/HTTP
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ Provides-Extra: requests
23
+ Requires-Dist: requests>=2.28; extra == "requests"
24
+
25
+ # nexium-storage
26
+
27
+ Official Python SDK for [NEXIUM Storage](https://nexiumai.io) — upload, serve and manage files via a simple API.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ pip install nexium-storage
33
+
34
+ # For file upload support:
35
+ pip install "nexium-storage[requests]"
36
+ ```
37
+
38
+ ## Quick start
39
+
40
+ ```python
41
+ import os
42
+ from nexium_storage import NexiumStorage
43
+
44
+ storage = NexiumStorage(api_key=os.getenv("NEXIUM_API_KEY"))
45
+
46
+ # Upload
47
+ with open("photo.jpg", "rb") as f:
48
+ file = storage.upload(bucket_id, f, "photo.jpg", "image/jpeg")
49
+ print(file.url)
50
+
51
+ # List
52
+ result = storage.list(bucket_id, search="photo")
53
+ for f in result.files:
54
+ print(f.filename, f.size_bytes)
55
+
56
+ # Download URL
57
+ url = storage.download(file.id)
58
+
59
+ # Rename
60
+ storage.rename(file.id, "new-name.jpg")
61
+
62
+ # Delete
63
+ storage.delete(file.id)
64
+ ```
65
+
66
+ ## Webhook verification
67
+
68
+ ```python
69
+ from nexium_storage import NexiumStorage, NexiumError
70
+
71
+ # Flask example
72
+ @app.route("/webhook", methods=["POST"])
73
+ def webhook():
74
+ try:
75
+ payload = NexiumStorage.verify_webhook(
76
+ request.get_data(),
77
+ request.headers.get("X-Nexium-Signature"),
78
+ os.getenv("NEXIUM_WEBHOOK_SECRET"),
79
+ )
80
+ print(payload["event"], payload["data"])
81
+ return "", 200
82
+ except NexiumError:
83
+ return "", 401
84
+ ```
85
+
86
+ ## License
87
+
88
+ MIT
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ nexium_storage/__init__.py
4
+ nexium_storage/client.py
5
+ nexium_storage.egg-info/PKG-INFO
6
+ nexium_storage.egg-info/SOURCES.txt
7
+ nexium_storage.egg-info/dependency_links.txt
8
+ nexium_storage.egg-info/requires.txt
9
+ nexium_storage.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+
2
+ [requests]
3
+ requests>=2.28
@@ -0,0 +1 @@
1
+ nexium_storage
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "nexium-storage"
7
+ version = "0.1.0"
8
+ description = "Official Python SDK for NEXIUM Storage"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "NEXIUM", email = "ai.nexium@gmail.com" }
14
+ ]
15
+ keywords = ["nexium", "storage", "s3", "r2", "file-upload", "cloud"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.8",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Internet :: WWW/HTTP",
26
+ "Topic :: Software Development :: Libraries :: Python Modules",
27
+ ]
28
+ dependencies = []
29
+
30
+ [project.optional-dependencies]
31
+ requests = ["requests>=2.28"]
32
+
33
+ [project.urls]
34
+ Homepage = "https://nexiumai.io"
35
+ Documentation = "https://console.nexiumai.io/docs"
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["."]
39
+ include = ["nexium_storage*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+