Uploading to Cloud Storage¶
The gpio publish upload command uploads files and directories to cloud object storage, supporting S3, GCS, Azure, and HTTP destinations.
Quick Start¶
# Upload single file to S3
gpio publish upload data.parquet s3://bucket/path/data.parquet
# Upload directory (preserves structure)
gpio publish upload output/ s3://bucket/dataset/
# With AWS profile
gpio publish upload data.parquet s3://bucket/data.parquet --aws-profile my-profile
import geoparquet_io as gpio
table = gpio.read("data.parquet")
# Upload to S3
table.upload("s3://bucket/path/data.parquet", profile="my-profile")
Supported Destinations¶
| Destination | URL Format | Example |
|---|---|---|
| Amazon S3 | s3:// |
s3://my-bucket/path/file.parquet |
| Google Cloud Storage | gs:// |
gs://my-bucket/path/file.parquet |
| Azure Blob Storage | az:// |
az://myaccount/mycontainer/path/file.parquet |
| HTTP/HTTPS | http:// or https:// |
https://api.example.com/upload |
S3 credentials come from the full AWS chain¶
An S3 upload resolves credentials through the standard AWS credential chain, the same one gpio's reads use: environment variables, ~/.aws/credentials (the profile named by --aws-profile or AWS_PROFILE), an aws sso login session, an assume-role profile (role_arn with source_profile, or a web identity token), credential_process, and EC2/ECS/EKS instance metadata (#865). Uploads and the byte-for-byte copies other commands make now share one resolution, so a command no longer succeeds on one branch and fails Access Denied on the other.
Credentials from SSO, assume-role or credential_process expire. gpio resolves them once when the command starts, which covers a single upload or copy; a run longer than the credential lifetime needs to be restarted.
Azure URLs name the account first¶
An Azure destination is az://<account>/<container>/<path> — the storage account, then the container, then the key. gpio builds the store from those two segments itself, so the account never has to be in the environment and is never mistaken for the container (#864).
The credential still comes from the environment, and gpio checks for one before it uploads:
# Storage account key
export AZURE_STORAGE_ACCOUNT_KEY=your_key
# ...or a SAS token
export AZURE_STORAGE_SAS_TOKEN=your_token
gpio publish upload data.parquet az://myaccount/mycontainer/data.parquet
AZURE_STORAGE_ACCESS_KEY, AZURE_STORAGE_SAS_KEY, the AZURE_STORAGE_CLIENT_* client-secret variables and AZURE_USE_AZURE_CLI=true are honoured too. The Azure CLI opt-in is explicit: az login alone is not picked up — set AZURE_USE_AZURE_CLI=true as well. AZURE_STORAGE_ACCOUNT_NAME is not needed — the account in the URL wins over it.
Directory Uploads¶
When uploading directories, gpio preserves the directory structure and uploads files in parallel:
# Upload all files
gpio publish upload output/ s3://bucket/dataset/
# Only parquet files
gpio publish upload output/ s3://bucket/dataset/ --pattern "*.parquet"
# Increase parallelism
gpio publish upload output/ s3://bucket/dataset/ --max-files 8
AWS Configuration¶
Using AWS Profiles¶
gpio publish upload data.parquet s3://bucket/data.parquet --aws-profile source-coop
S3-Compatible Endpoints¶
For MinIO, Wasabi, or other S3-compatible storage:
gpio publish upload data.parquet s3://bucket/data.parquet \
--s3-endpoint minio.example.com:9000 \
--s3-region us-east-1
Disable SSL¶
For local development or non-SSL endpoints:
gpio publish upload data.parquet s3://bucket/data.parquet \
--s3-endpoint localhost:9000 \
--s3-no-ssl
Multipart Uploads¶
Large files are automatically uploaded using multipart uploads:
# Customize chunk settings
gpio publish upload large.parquet s3://bucket/large.parquet \
--chunk-size 104857600 \
--chunk-concurrency 12
Error Handling¶
By default, directory uploads continue on errors. Use --fail-fast to stop on first error:
gpio publish upload output/ s3://bucket/dataset/ --fail-fast
Either way, the summary counts only the files that actually reached the store,
and a directory upload that left anything out exits 1 — so && and set -e
stop the script instead of carrying on over an incomplete dataset:
==================================================
✓ 2/10 file(s) uploaded successfully
✗ 1 file(s) failed
⊘ 7 file(s) not attempted (stopped on first error)
--fail-fast stops every file that has not started yet; uploads already in
flight are allowed to finish, and are counted by whether they arrived. The three
counts always add up to the total, so a retry knows exactly how much is missing.
Partial and total failure both exit 1 — read the counts, not the exit code, to
tell them apart.
In Python the same failure raises RemoteAccessError:
from pathlib import Path
from geoparquet_io.core.exceptions import RemoteAccessError
from geoparquet_io.core.upload import upload
try:
upload(Path("output/"), "s3://bucket/dataset/", fail_fast=True)
except RemoteAccessError as e:
print(f"upload incomplete: {e}")
Dry Run¶
Preview what would be uploaded without actually uploading:
gpio publish upload output/ s3://bucket/dataset/ --dry-run
CLI Reference¶
See the CLI Reference for complete options.