herfy-gcs 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,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: herfy-gcs
3
+ Version: 0.1.0
4
+ Summary: GCS storage SDK for Herfy applications — upload/download via Control Center
5
+ Author: Herfy Development Team
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/herfy/herfy-shared
8
+ Project-URL: Documentation, https://github.com/herfy/herfy-shared/tree/main/herfy-gcs
9
+ Keywords: gcs,storage,upload,download,google-cloud,herfy
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
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.9
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: httpx>=0.24.0
23
+ Provides-Extra: fastapi
24
+ Requires-Dist: fastapi>=0.100.0; extra == "fastapi"
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
27
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
28
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
29
+ Requires-Dist: respx>=0.20.0; extra == "dev"
30
+ Requires-Dist: black>=23.0.0; extra == "dev"
31
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
32
+
33
+ # herfy-gcs
34
+
35
+ GCS storage SDK for Herfy applications.
36
+ Files are uploaded to and downloaded from Google Cloud Storage **through the Herfy Control Center** — apps never handle raw GCS credentials directly.
37
+
38
+ ## How it works
39
+
40
+ ```
41
+ App ──(client_id + secret)──► Control Center ──(service account)──► GCS bucket
42
+ ```
43
+
44
+ 1. Your app registered with the Control Center and received a `client_id` + `client_secret`.
45
+ 2. The SDK exchanges those credentials for a short-lived CC access token (OAuth2 client credentials flow).
46
+ 3. Upload / download requests go to the CC storage API; the CC proxies them to GCS under its own service account.
47
+ 4. The CC enforces per-app bucket/prefix scoping — your app can only touch its own folder.
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ # From PyPI (once published)
53
+ pip install herfy-gcs==0.1.0
54
+
55
+ # Local editable install (monorepo / during development)
56
+ pip install -e /path/to/herfy-shared/herfy-gcs
57
+ ```
58
+
59
+ ## Quick start
60
+
61
+ ### Environment variables
62
+
63
+ ```bash
64
+ CONTROL_CENTER_URL=https://controlcenter-xxx.run.app
65
+ AUTH_CLIENT_ID=app_your_client_id # from CC registration
66
+ AUTH_CLIENT_SECRET=your_client_secret # from CC registration
67
+ ```
68
+
69
+ Optional overrides:
70
+
71
+ | Variable | Default | Description |
72
+ |------------------------|----------------|----------------------------------------------|
73
+ | `GCS_STORAGE_API_PATH` | `/api/storage` | CC storage endpoint prefix |
74
+ | `GCS_TOKEN_CACHE_TTL` | `240` | Seconds to cache the CC token |
75
+ | `GCS_UPLOAD_TIMEOUT` | `120` | HTTP timeout for upload/download (seconds) |
76
+ | `GCS_REQUEST_TIMEOUT` | `15` | HTTP timeout for metadata requests (seconds) |
77
+
78
+ ### Sync client (default — works in FastAPI sync endpoints)
79
+
80
+ ```python
81
+ from herfy_gcs import HerfyGCSClient
82
+
83
+ client = HerfyGCSClient.from_env()
84
+
85
+ # Upload a file
86
+ with open("deposit.pdf", "rb") as f:
87
+ result = client.upload(
88
+ content=f,
89
+ filename="deposit.pdf",
90
+ content_type="application/pdf",
91
+ folder="daily-cash/2025",
92
+ )
93
+
94
+ print(result.file_ref) # store this in your database
95
+
96
+ # Get a signed download URL (valid for 1 hour)
97
+ url = client.get_download_url(result.file_ref, expires_in=3600)
98
+
99
+ # Download raw bytes
100
+ data = client.download(result.file_ref)
101
+
102
+ # Resolve any existing reference (GCS URL or CC ref) to a usable URL
103
+ url = client.resolve_url(existing_db_ref)
104
+
105
+ # Delete a file
106
+ client.delete(result.file_ref)
107
+ ```
108
+
109
+ ### Async client (for async FastAPI endpoints)
110
+
111
+ ```python
112
+ from herfy_gcs import AsyncHerfyGCSClient
113
+
114
+ client = AsyncHerfyGCSClient.from_env()
115
+
116
+ result = await client.upload(content=pdf_bytes, filename="doc.pdf", folder="daily-cash")
117
+ url = await client.get_download_url(result.file_ref)
118
+ data = await client.download(result.file_ref)
119
+ ```
120
+
121
+ ### Explicit credentials (no env vars)
122
+
123
+ ```python
124
+ client = HerfyGCSClient.from_credentials(
125
+ client_id="app_7c144431f50c",
126
+ client_secret="your-secret",
127
+ control_center_url="https://controlcenter-xxx.run.app",
128
+ )
129
+ ```
130
+
131
+ ### FastAPI dependency example
132
+
133
+ ```python
134
+ from functools import lru_cache
135
+ from fastapi import Depends
136
+ from herfy_gcs import HerfyGCSClient
137
+
138
+ @lru_cache(maxsize=1)
139
+ def get_gcs_client() -> HerfyGCSClient:
140
+ return HerfyGCSClient.from_env()
141
+
142
+ @app.post("/upload")
143
+ async def upload_file(
144
+ file: UploadFile,
145
+ gcs: HerfyGCSClient = Depends(get_gcs_client),
146
+ ):
147
+ result = gcs.upload(
148
+ content=await file.read(),
149
+ filename=file.filename,
150
+ content_type=file.content_type or "application/octet-stream",
151
+ folder="daily-cash",
152
+ )
153
+ return {"file_ref": result.file_ref, "url": result.url}
154
+ ```
155
+
156
+ ## Control Center storage API
157
+
158
+ The CC must expose these endpoints (authenticated with a CC bearer token):
159
+
160
+ | Method | Path | Description |
161
+ |----------|--------------------------|---------------------------------|
162
+ | `POST` | `/api/storage/upload` | Multipart file upload |
163
+ | `POST` | `/api/storage/signed-url`| Generate a signed download URL |
164
+ | `GET` | `/api/storage/file` | Get file metadata (`?ref=<ref>`) |
165
+ | `GET` | `/api/storage/download` | Stream file bytes (`?ref=<ref>`) |
166
+ | `DELETE` | `/api/storage/file` | Delete a file (`?ref=<ref>`) |
167
+
168
+ ### Upload request
169
+
170
+ ```
171
+ POST /api/storage/upload
172
+ Authorization: Bearer <cc_token>
173
+ Content-Type: multipart/form-data
174
+
175
+ file=<bytes>
176
+ filename=deposit.pdf
177
+ folder=daily-cash/2025
178
+ ```
179
+
180
+ Response `200 OK`:
181
+ ```json
182
+ {
183
+ "file_ref": "cc:bucket_herfy_datacloud/daily-cash/2025/deposit.pdf",
184
+ "url": "https://storage.googleapis.com/bucket_herfy_datacloud/...",
185
+ "filename": "deposit.pdf",
186
+ "content_type": "application/pdf",
187
+ "size": 204800,
188
+ "folder": "daily-cash/2025"
189
+ }
190
+ ```
191
+
192
+ ### Signed URL request
193
+
194
+ ```
195
+ POST /api/storage/signed-url
196
+ Authorization: Bearer <cc_token>
197
+ Content-Type: application/json
198
+
199
+ {"file_ref": "cc:...", "expires_in": 3600}
200
+ ```
201
+
202
+ Response `200 OK`:
203
+ ```json
204
+ {"url": "https://storage.googleapis.com/...?X-Goog-Signature=...", "file_ref": "cc:...", "expires_in": 3600}
205
+ ```
206
+
207
+ ## Error handling
208
+
209
+ ```python
210
+ from herfy_gcs import HerfyGCSClient, UploadError, AuthError, FileNotFoundError, GCSError
211
+
212
+ client = HerfyGCSClient.from_env()
213
+
214
+ try:
215
+ result = client.upload(content=data, filename="doc.pdf")
216
+ except AuthError as e:
217
+ # Invalid client_id/secret or CC unreachable
218
+ print(f"Auth failed: {e.message}")
219
+ except UploadError as e:
220
+ print(f"Upload failed (HTTP {e.status_code}): {e.message}")
221
+ except GCSError as e:
222
+ print(f"Storage error: {e.message}")
223
+ ```
224
+
225
+ ## License
226
+
227
+ MIT
@@ -0,0 +1,195 @@
1
+ # herfy-gcs
2
+
3
+ GCS storage SDK for Herfy applications.
4
+ Files are uploaded to and downloaded from Google Cloud Storage **through the Herfy Control Center** — apps never handle raw GCS credentials directly.
5
+
6
+ ## How it works
7
+
8
+ ```
9
+ App ──(client_id + secret)──► Control Center ──(service account)──► GCS bucket
10
+ ```
11
+
12
+ 1. Your app registered with the Control Center and received a `client_id` + `client_secret`.
13
+ 2. The SDK exchanges those credentials for a short-lived CC access token (OAuth2 client credentials flow).
14
+ 3. Upload / download requests go to the CC storage API; the CC proxies them to GCS under its own service account.
15
+ 4. The CC enforces per-app bucket/prefix scoping — your app can only touch its own folder.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ # From PyPI (once published)
21
+ pip install herfy-gcs==0.1.0
22
+
23
+ # Local editable install (monorepo / during development)
24
+ pip install -e /path/to/herfy-shared/herfy-gcs
25
+ ```
26
+
27
+ ## Quick start
28
+
29
+ ### Environment variables
30
+
31
+ ```bash
32
+ CONTROL_CENTER_URL=https://controlcenter-xxx.run.app
33
+ AUTH_CLIENT_ID=app_your_client_id # from CC registration
34
+ AUTH_CLIENT_SECRET=your_client_secret # from CC registration
35
+ ```
36
+
37
+ Optional overrides:
38
+
39
+ | Variable | Default | Description |
40
+ |------------------------|----------------|----------------------------------------------|
41
+ | `GCS_STORAGE_API_PATH` | `/api/storage` | CC storage endpoint prefix |
42
+ | `GCS_TOKEN_CACHE_TTL` | `240` | Seconds to cache the CC token |
43
+ | `GCS_UPLOAD_TIMEOUT` | `120` | HTTP timeout for upload/download (seconds) |
44
+ | `GCS_REQUEST_TIMEOUT` | `15` | HTTP timeout for metadata requests (seconds) |
45
+
46
+ ### Sync client (default — works in FastAPI sync endpoints)
47
+
48
+ ```python
49
+ from herfy_gcs import HerfyGCSClient
50
+
51
+ client = HerfyGCSClient.from_env()
52
+
53
+ # Upload a file
54
+ with open("deposit.pdf", "rb") as f:
55
+ result = client.upload(
56
+ content=f,
57
+ filename="deposit.pdf",
58
+ content_type="application/pdf",
59
+ folder="daily-cash/2025",
60
+ )
61
+
62
+ print(result.file_ref) # store this in your database
63
+
64
+ # Get a signed download URL (valid for 1 hour)
65
+ url = client.get_download_url(result.file_ref, expires_in=3600)
66
+
67
+ # Download raw bytes
68
+ data = client.download(result.file_ref)
69
+
70
+ # Resolve any existing reference (GCS URL or CC ref) to a usable URL
71
+ url = client.resolve_url(existing_db_ref)
72
+
73
+ # Delete a file
74
+ client.delete(result.file_ref)
75
+ ```
76
+
77
+ ### Async client (for async FastAPI endpoints)
78
+
79
+ ```python
80
+ from herfy_gcs import AsyncHerfyGCSClient
81
+
82
+ client = AsyncHerfyGCSClient.from_env()
83
+
84
+ result = await client.upload(content=pdf_bytes, filename="doc.pdf", folder="daily-cash")
85
+ url = await client.get_download_url(result.file_ref)
86
+ data = await client.download(result.file_ref)
87
+ ```
88
+
89
+ ### Explicit credentials (no env vars)
90
+
91
+ ```python
92
+ client = HerfyGCSClient.from_credentials(
93
+ client_id="app_7c144431f50c",
94
+ client_secret="your-secret",
95
+ control_center_url="https://controlcenter-xxx.run.app",
96
+ )
97
+ ```
98
+
99
+ ### FastAPI dependency example
100
+
101
+ ```python
102
+ from functools import lru_cache
103
+ from fastapi import Depends
104
+ from herfy_gcs import HerfyGCSClient
105
+
106
+ @lru_cache(maxsize=1)
107
+ def get_gcs_client() -> HerfyGCSClient:
108
+ return HerfyGCSClient.from_env()
109
+
110
+ @app.post("/upload")
111
+ async def upload_file(
112
+ file: UploadFile,
113
+ gcs: HerfyGCSClient = Depends(get_gcs_client),
114
+ ):
115
+ result = gcs.upload(
116
+ content=await file.read(),
117
+ filename=file.filename,
118
+ content_type=file.content_type or "application/octet-stream",
119
+ folder="daily-cash",
120
+ )
121
+ return {"file_ref": result.file_ref, "url": result.url}
122
+ ```
123
+
124
+ ## Control Center storage API
125
+
126
+ The CC must expose these endpoints (authenticated with a CC bearer token):
127
+
128
+ | Method | Path | Description |
129
+ |----------|--------------------------|---------------------------------|
130
+ | `POST` | `/api/storage/upload` | Multipart file upload |
131
+ | `POST` | `/api/storage/signed-url`| Generate a signed download URL |
132
+ | `GET` | `/api/storage/file` | Get file metadata (`?ref=<ref>`) |
133
+ | `GET` | `/api/storage/download` | Stream file bytes (`?ref=<ref>`) |
134
+ | `DELETE` | `/api/storage/file` | Delete a file (`?ref=<ref>`) |
135
+
136
+ ### Upload request
137
+
138
+ ```
139
+ POST /api/storage/upload
140
+ Authorization: Bearer <cc_token>
141
+ Content-Type: multipart/form-data
142
+
143
+ file=<bytes>
144
+ filename=deposit.pdf
145
+ folder=daily-cash/2025
146
+ ```
147
+
148
+ Response `200 OK`:
149
+ ```json
150
+ {
151
+ "file_ref": "cc:bucket_herfy_datacloud/daily-cash/2025/deposit.pdf",
152
+ "url": "https://storage.googleapis.com/bucket_herfy_datacloud/...",
153
+ "filename": "deposit.pdf",
154
+ "content_type": "application/pdf",
155
+ "size": 204800,
156
+ "folder": "daily-cash/2025"
157
+ }
158
+ ```
159
+
160
+ ### Signed URL request
161
+
162
+ ```
163
+ POST /api/storage/signed-url
164
+ Authorization: Bearer <cc_token>
165
+ Content-Type: application/json
166
+
167
+ {"file_ref": "cc:...", "expires_in": 3600}
168
+ ```
169
+
170
+ Response `200 OK`:
171
+ ```json
172
+ {"url": "https://storage.googleapis.com/...?X-Goog-Signature=...", "file_ref": "cc:...", "expires_in": 3600}
173
+ ```
174
+
175
+ ## Error handling
176
+
177
+ ```python
178
+ from herfy_gcs import HerfyGCSClient, UploadError, AuthError, FileNotFoundError, GCSError
179
+
180
+ client = HerfyGCSClient.from_env()
181
+
182
+ try:
183
+ result = client.upload(content=data, filename="doc.pdf")
184
+ except AuthError as e:
185
+ # Invalid client_id/secret or CC unreachable
186
+ print(f"Auth failed: {e.message}")
187
+ except UploadError as e:
188
+ print(f"Upload failed (HTTP {e.status_code}): {e.message}")
189
+ except GCSError as e:
190
+ print(f"Storage error: {e.message}")
191
+ ```
192
+
193
+ ## License
194
+
195
+ MIT
@@ -0,0 +1,54 @@
1
+ """
2
+ Herfy GCS SDK — upload and download files via the Herfy Control Center.
3
+
4
+ Apps authenticate using their CC-registered client_id and client_secret.
5
+ The Control Center manages GCS bucket access and enforces per-app scoping.
6
+
7
+ Quick start::
8
+
9
+ from herfy_gcs import HerfyGCSClient
10
+
11
+ client = HerfyGCSClient.from_env() # reads AUTH_CLIENT_ID / AUTH_CLIENT_SECRET
12
+
13
+ # Upload
14
+ result = client.upload(content=pdf_bytes, filename="deposit.pdf", folder="daily-cash")
15
+ file_ref = result.file_ref # store in database
16
+
17
+ # Download URL (signed, expires in 1 hour)
18
+ url = client.get_download_url(file_ref, expires_in=3600)
19
+
20
+ # Or resolve any existing ref (GCS URL or CC ref) to a usable URL
21
+ url = client.resolve_url(existing_ref)
22
+ """
23
+
24
+ from .client import AsyncHerfyGCSClient, HerfyGCSClient
25
+ from .exceptions import (
26
+ AuthError,
27
+ ConfigurationError,
28
+ DownloadError,
29
+ FileNotFoundError,
30
+ GCSError,
31
+ UploadError,
32
+ )
33
+ from .models import FileInfo, GCSConfig, SignedUrlResult, UploadResult
34
+
35
+ __version__ = "0.1.0"
36
+
37
+ __all__ = [
38
+ # Clients
39
+ "HerfyGCSClient",
40
+ "AsyncHerfyGCSClient",
41
+ # Config
42
+ "GCSConfig",
43
+ # Models
44
+ "UploadResult",
45
+ "FileInfo",
46
+ "SignedUrlResult",
47
+ # Exceptions
48
+ "GCSError",
49
+ "AuthError",
50
+ "UploadError",
51
+ "DownloadError",
52
+ "FileNotFoundError",
53
+ "ConfigurationError",
54
+ ]