GCS storage SDK for Herfy applications — upload/download via Control Center
Project description
herfy-gcs
GCS storage SDK for Herfy applications.
Files are uploaded to and downloaded from Google Cloud Storage through the Herfy Control Center — apps never handle raw GCS credentials directly.
How it works
App ──(client_id + secret)──► Control Center ──(service account)──► GCS bucket
- Your app registered with the Control Center and received a
client_id+client_secret. - The SDK exchanges those credentials for a short-lived CC access token (OAuth2 client credentials flow).
- Upload / download requests go to the CC storage API; the CC proxies them to GCS under its own service account.
- The CC enforces per-app bucket/prefix scoping — your app can only touch its own folder.
Installation
# From PyPI (once published)
pip install herfy-gcs==0.1.0
# Local editable install (monorepo / during development)
pip install -e /path/to/herfy-shared/herfy-gcs
Quick start
Environment variables
CONTROL_CENTER_URL=https://controlcenter-xxx.run.app
AUTH_CLIENT_ID=app_your_client_id # from CC registration
AUTH_CLIENT_SECRET=your_client_secret # from CC registration
Optional overrides:
| Variable | Default | Description |
|---|---|---|
GCS_STORAGE_API_PATH |
/api/storage |
CC storage endpoint prefix |
GCS_TOKEN_CACHE_TTL |
240 |
Seconds to cache the CC token |
GCS_UPLOAD_TIMEOUT |
120 |
HTTP timeout for upload/download (seconds) |
GCS_REQUEST_TIMEOUT |
15 |
HTTP timeout for metadata requests (seconds) |
Sync client (default — works in FastAPI sync endpoints)
from herfy_gcs import HerfyGCSClient
client = HerfyGCSClient.from_env()
# Upload a file
with open("deposit.pdf", "rb") as f:
result = client.upload(
content=f,
filename="deposit.pdf",
content_type="application/pdf",
folder="daily-cash/2025",
)
print(result.file_ref) # store this in your database
# Get a signed download URL (valid for 1 hour)
url = client.get_download_url(result.file_ref, expires_in=3600)
# Download raw bytes
data = client.download(result.file_ref)
# Resolve any existing reference (GCS URL or CC ref) to a usable URL
url = client.resolve_url(existing_db_ref)
# Delete a file
client.delete(result.file_ref)
Async client (for async FastAPI endpoints)
from herfy_gcs import AsyncHerfyGCSClient
client = AsyncHerfyGCSClient.from_env()
result = await client.upload(content=pdf_bytes, filename="doc.pdf", folder="daily-cash")
url = await client.get_download_url(result.file_ref)
data = await client.download(result.file_ref)
Explicit credentials (no env vars)
client = HerfyGCSClient.from_credentials(
client_id="app_7c144431f50c",
client_secret="your-secret",
control_center_url="https://controlcenter-xxx.run.app",
)
FastAPI dependency example
from functools import lru_cache
from fastapi import Depends
from herfy_gcs import HerfyGCSClient
@lru_cache(maxsize=1)
def get_gcs_client() -> HerfyGCSClient:
return HerfyGCSClient.from_env()
@app.post("/upload")
async def upload_file(
file: UploadFile,
gcs: HerfyGCSClient = Depends(get_gcs_client),
):
result = gcs.upload(
content=await file.read(),
filename=file.filename,
content_type=file.content_type or "application/octet-stream",
folder="daily-cash",
)
return {"file_ref": result.file_ref, "url": result.url}
Control Center storage API
The CC must expose these endpoints (authenticated with a CC bearer token):
| Method | Path | Description |
|---|---|---|
POST |
/api/storage/upload |
Multipart file upload |
POST |
/api/storage/signed-url |
Generate a signed download URL |
GET |
/api/storage/file |
Get file metadata (?ref=<ref>) |
GET |
/api/storage/download |
Stream file bytes (?ref=<ref>) |
DELETE |
/api/storage/file |
Delete a file (?ref=<ref>) |
Upload request
POST /api/storage/upload
Authorization: Bearer <cc_token>
Content-Type: multipart/form-data
file=<bytes>
filename=deposit.pdf
folder=daily-cash/2025
Response 200 OK:
{
"file_ref": "cc:bucket_herfy_datacloud/daily-cash/2025/deposit.pdf",
"url": "https://storage.googleapis.com/bucket_herfy_datacloud/...",
"filename": "deposit.pdf",
"content_type": "application/pdf",
"size": 204800,
"folder": "daily-cash/2025"
}
Signed URL request
POST /api/storage/signed-url
Authorization: Bearer <cc_token>
Content-Type: application/json
{"file_ref": "cc:...", "expires_in": 3600}
Response 200 OK:
{"url": "https://storage.googleapis.com/...?X-Goog-Signature=...", "file_ref": "cc:...", "expires_in": 3600}
Error handling
from herfy_gcs import HerfyGCSClient, UploadError, AuthError, FileNotFoundError, GCSError
client = HerfyGCSClient.from_env()
try:
result = client.upload(content=data, filename="doc.pdf")
except AuthError as e:
# Invalid client_id/secret or CC unreachable
print(f"Auth failed: {e.message}")
except UploadError as e:
print(f"Upload failed (HTTP {e.status_code}): {e.message}")
except GCSError as e:
print(f"Storage error: {e.message}")
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file herfy_gcs-0.1.0.tar.gz.
File metadata
- Download URL: herfy_gcs-0.1.0.tar.gz
- Upload date:
- Size: 11.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
96ade3fde0634c7e14efd67bac3be9d9ca28e16967262421b2689791cd5af3cd
|
|
| MD5 |
498a8957afe36c77ea5e358192f84a64
|
|
| BLAKE2b-256 |
f21bce8a8d4a2830598324927cff8f1cb357f3013b8f94e641f16abe86614157
|
Provenance
The following attestation bundles were made for herfy_gcs-0.1.0.tar.gz:
Publisher:
publish-gcs-sdk.yml on Herfy-Food-Services/herfy-shared-library
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
herfy_gcs-0.1.0.tar.gz -
Subject digest:
96ade3fde0634c7e14efd67bac3be9d9ca28e16967262421b2689791cd5af3cd - Sigstore transparency entry: 1809168280
- Sigstore integration time:
-
Permalink:
Herfy-Food-Services/herfy-shared-library@5f2f7883384f5acc96cabf7bb5cd750ed05ff220 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/Herfy-Food-Services
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-gcs-sdk.yml@5f2f7883384f5acc96cabf7bb5cd750ed05ff220 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file herfy_gcs-0.1.0-py3-none-any.whl.
File metadata
- Download URL: herfy_gcs-0.1.0-py3-none-any.whl
- Upload date:
- Size: 10.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
af524800dafb773444c451b3f28a15949d9e2d85d1e8abc51e01733f241ab1ef
|
|
| MD5 |
db80bddce908c4954697a5f1ef765a62
|
|
| BLAKE2b-256 |
a8ac535f1b4def5ca6ff9ab5a6d32cfc92698fa1a27b23bd0f49658b268991dc
|
Provenance
The following attestation bundles were made for herfy_gcs-0.1.0-py3-none-any.whl:
Publisher:
publish-gcs-sdk.yml on Herfy-Food-Services/herfy-shared-library
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
herfy_gcs-0.1.0-py3-none-any.whl -
Subject digest:
af524800dafb773444c451b3f28a15949d9e2d85d1e8abc51e01733f241ab1ef - Sigstore transparency entry: 1809168309
- Sigstore integration time:
-
Permalink:
Herfy-Food-Services/herfy-shared-library@5f2f7883384f5acc96cabf7bb5cd750ed05ff220 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/Herfy-Food-Services
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-gcs-sdk.yml@5f2f7883384f5acc96cabf7bb5cd750ed05ff220 -
Trigger Event:
workflow_dispatch
-
Statement type: