Minimal async Google Cloud Storage + Auth client built on httpx (HTTP/2).
Project description
gcshttpx
Minimal, secure async Google Cloud Storage client built on httpx with native HTTP/2 support.
Why gcshttpx?
- 🚀 Fast: Built on httpx with HTTP/2 support for connection multiplexing
- 🔒 Secure: Explicit credential handling, no automatic filesystem searches
- 🪶 Lightweight: Minimal dependencies (httpx, orjson, PyJWT, cryptography)
- ⚡ Async-first: Full async/await support with modern Python
- 🎯 Simple: Clean, intuitive API for common operations
- 📦 Type-safe: Fully typed with py.typed marker
Installation
# Using uv (recommended)
uv add gcshttpx
# Using pip
pip install gcshttpx
Quick Start
import asyncio
from gcshttpx import Storage, Token
async def main():
# Create authenticated token
token = Token(
service_file="path/to/service-account.json",
scopes=["https://www.googleapis.com/auth/cloud-platform"]
)
# Create storage client
storage = Storage(token=token)
# Upload a file
await storage.upload("my-bucket", "hello.txt", b"Hello, World!")
# Download a file
data = await storage.download("my-bucket", "hello.txt")
print(data) # b"Hello, World!"
# List objects
items = await storage.list_objects("my-bucket", params={"prefix": "logs/"})
for item in items["items"]:
print(item["name"])
# Clean up
await storage.close()
asyncio.run(main())
Authentication
Service Account (Recommended for Production)
from gcshttpx import Token
# From file path
token = Token(
service_file="/path/to/service-account.json",
scopes=["https://www.googleapis.com/auth/devstorage.read_write"]
)
# From environment variable
# Set GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
token = Token(scopes=["https://www.googleapis.com/auth/cloud-platform"])
# From file-like object
import io
import json
# Do not use this pattern in production
credentials = {
"type": "service_account",
"project_id": "my-project",
"private_key_id": "key-id",
"private_key": "-----BEGIN PRIVATE KEY-----\n...",
"client_email": "service@my-project.iam.gserviceaccount.com",
"client_id": "123456789",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
}
token = Token(
service_file=io.StringIO(json.dumps(credentials)),
scopes=["https://www.googleapis.com/auth/cloud-platform"]
)
GCE Metadata Server (For GCP Environments)
from gcshttpx import Token
# Automatically uses GCE metadata service if no credentials provided
token = Token(scopes=["https://www.googleapis.com/auth/cloud-platform"])
Security Notes
⚠️ gcshttpx takes security seriously:
- HTTPS-only token endpoints: Prevents credential leakage
- Input validation: All credentials are validated before use
- No sensitive data in logs: Errors don't expose credentials
Storage Operations
Upload
# Simple upload (bytes or str)
await storage.upload("bucket", "file.txt", b"content")
# With metadata
await storage.upload(
"bucket",
"file.json",
b'{"key": "value"}',
content_type="application/json",
metadata={"cacheControl": "no-cache", "contentLanguage": "en"}
)
# From file
await storage.upload_from_filename("bucket", "remote.txt", "local.txt")
# Gzip compression
await storage.upload("bucket", "file.txt", b"content", zipped=True)
# Force resumable upload (for large files)
await storage.upload(
"bucket",
"large-file.bin",
large_data,
force_resumable_upload=True
)
Download
# Download to bytes
data = await storage.download("bucket", "file.txt")
# Download to file
await storage.download_to_filename("bucket", "remote.txt", "local.txt")
# Download metadata only
metadata = await storage.download_metadata("bucket", "file.txt")
print(metadata["size"], metadata["contentType"])
# Stream large files
stream = await storage.download_stream("bucket", "large-file.bin")
while chunk := await stream.read(8192):
process(chunk)
List & Search
# List all objects
result = await storage.list_objects("bucket")
for item in result["items"]:
print(item["name"], item["size"])
# With prefix filter
result = await storage.list_objects("bucket", params={"prefix": "logs/2024/"})
# List with delimiter (folder-like)
result = await storage.list_objects("bucket", params={"delimiter": "/"})
print("Prefixes:", result.get("prefixes")) # 'folders'
print("Items:", result.get("items")) # files in current 'folder'
# List buckets
buckets = await storage.list_buckets("my-project-id")
for bucket in buckets:
print(bucket.name)
Delete
# Delete object
await storage.delete("bucket", "file.txt")
# Delete returns status text
status = await storage.delete("bucket", "file.txt")
print(status) # "OK" or error message
Object Operations
# Check if object exists
exists = await storage.blob_exists("bucket", "file.txt")
# Compose multiple objects into one
await storage.compose(
"bucket",
"merged.txt",
["part1.txt", "part2.txt", "part3.txt"],
content_type="text/plain"
)
# Update metadata
await storage.patch_metadata(
"bucket",
"file.txt",
{"cacheControl": "public, max-age=3600"}
)
# Get bucket metadata
metadata = await storage.get_bucket_metadata("bucket")
print(metadata["location"], metadata["storageClass"])
Signed URLs
from gcshttpx import IamClient
# Create IAM client for signing
iam = IamClient(token=token)
# Generate signed URL (valid for 1 hour)
bucket = storage.get_bucket("my-bucket")
blob = await bucket.get_blob("file.txt")
signed_url = await blob.get_signed_url(
expiration=3600,
iam_client=iam
)
# Share the URL
print(f"Download link: {signed_url}")
Bucket and Blob Objects
# Get bucket object
bucket = storage.get_bucket("my-bucket")
metadata = await bucket.get_metadata()
exists = await bucket.blob_exists("file.txt")
# Get blob object
blob = await bucket.get_blob("file.txt")
await blob.upload(b"new content", content_type="text/plain")
data = await blob.download()
await blob.delete()
Advanced Usage
Custom HTTP Client
import httpx
from gcshttpx import Storage, Token
# Use custom httpx client with specific settings
async with httpx.AsyncClient(
http2=True,
timeout=httpx.Timeout(30.0),
limits=httpx.Limits(max_keepalive_connections=5)
) as client:
token = Token(service_file="credentials.json")
storage = Storage(session=client, token=token)
await storage.upload("bucket", "file.txt", b"content")
Token Management
from gcshttpx import Token
token = Token(
service_file="credentials.json",
scopes=["https://www.googleapis.com/auth/cloud-platform"],
# Refresh token when 50% of lifetime has passed
background_refresh_after=0.5,
# Force refresh when 95% of lifetime has passed
force_refresh_after=0.95
)
# Manually get current token
access_token = await token.get()
# Get project ID
project_id = await token.get_project()
# Close token session
await token.close()
Error Handling
import httpx
from gcshttpx import Storage
try:
data = await storage.download("bucket", "nonexistent.txt")
except httpx.HTTPStatusError as e:
print(f"HTTP {e.response.status_code}: {e.response.text}")
except httpx.TimeoutException:
print("Request timed out")
except ValueError as e:
print(f"Validation error: {e}")
Common OAuth Scopes
# Full control
"https://www.googleapis.com/auth/cloud-platform"
# Read/write access to storage
"https://www.googleapis.com/auth/devstorage.read_write"
# Read-only access
"https://www.googleapis.com/auth/devstorage.read_only"
# Write-only access
"https://www.googleapis.com/auth/devstorage.write_only"
Development
# Clone repository
git clone https://github.com/piotrpenar/gcshttpx.git
cd gcshttpx
# Install with uv
uv sync
# Run tests
uv run pytest
# Run tests with coverage
uv run pytest --cov=gcshttpx
# Lint code
uv run ruff check .
# Format code
uv run ruff format .
# Build package
uv build
Requirements
- Python 3.10+
- httpx[http2] >= 0.27.0
- orjson >= 3.10.0
- PyJWT >= 2.9.0
- cryptography >= 43.0.0
License
MIT License - see LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Links
- Documentation: README.md
- Source Code: GitHub
- Issue Tracker: GitHub Issues
- PyPI: gcshttpx
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 gcshttpx-0.1.5.tar.gz.
File metadata
- Download URL: gcshttpx-0.1.5.tar.gz
- Upload date:
- Size: 16.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
958eae57cc3640c792d9bc3973129cd5b1002d005a38364735c2aa593645a8b9
|
|
| MD5 |
b9f59c61e8b611d3989e7a75df4b7073
|
|
| BLAKE2b-256 |
bcd7140b732ca3b70d637a7725eca5962b4f0ccd3fdedf883015b1e2e6791bab
|
Provenance
The following attestation bundles were made for gcshttpx-0.1.5.tar.gz:
Publisher:
publish.yml on piotrpenar/gcshttpx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gcshttpx-0.1.5.tar.gz -
Subject digest:
958eae57cc3640c792d9bc3973129cd5b1002d005a38364735c2aa593645a8b9 - Sigstore transparency entry: 843421548
- Sigstore integration time:
-
Permalink:
piotrpenar/gcshttpx@091a4d0f98fcccc8147950ac75dab8d3e50d1750 -
Branch / Tag:
refs/tags/v0.1.5 - Owner: https://github.com/piotrpenar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@091a4d0f98fcccc8147950ac75dab8d3e50d1750 -
Trigger Event:
release
-
Statement type:
File details
Details for the file gcshttpx-0.1.5-py3-none-any.whl.
File metadata
- Download URL: gcshttpx-0.1.5-py3-none-any.whl
- Upload date:
- Size: 18.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c109afd0658d9d1e8e85b9cd4ce6b21893dcbf513997cf3968cbc9ad9636a734
|
|
| MD5 |
c4b7f5c711a2f3a31c29041abbbd8fdf
|
|
| BLAKE2b-256 |
d82bcac05ac9c542d7be954e940335b961037821d0676912c4a18bf8f0a4262b
|
Provenance
The following attestation bundles were made for gcshttpx-0.1.5-py3-none-any.whl:
Publisher:
publish.yml on piotrpenar/gcshttpx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gcshttpx-0.1.5-py3-none-any.whl -
Subject digest:
c109afd0658d9d1e8e85b9cd4ce6b21893dcbf513997cf3968cbc9ad9636a734 - Sigstore transparency entry: 843421551
- Sigstore integration time:
-
Permalink:
piotrpenar/gcshttpx@091a4d0f98fcccc8147950ac75dab8d3e50d1750 -
Branch / Tag:
refs/tags/v0.1.5 - Owner: https://github.com/piotrpenar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@091a4d0f98fcccc8147950ac75dab8d3e50d1750 -
Trigger Event:
release
-
Statement type: