Read from S3 with credentials ¶
When to use this ¶
Your data lives on cloud storage and you want the same lazy-load surface you get from a local directory — predicate pushdown, column pruning, no full download. Credentials should come from whichever channel your environment already manages (CI secret, keyring, ~/.netrc) without ever being baked into source.
Quick example ¶
Open a package directly from S3 by URL. The default AWS credential chain (env vars, ~/.aws/credentials, IAM role) is tried automatically — you only pass credential= when you need to override it.
from netstead import Network
net = Network.from_source("s3://my-bucket/networks/leavenworth/")
print(f"{net.spec_version}: {net.links.count()} links")
Expected (will vary by network):
If your bucket is public-readable, no credential resolution runs at all. Either way the load is lazy — net.links is an ibis expression, not a materialised frame.
Step-by-step ¶
1. Install ¶
Cloud filesystem drivers (fsspec, s3fs, adlfs, gcsfs) come with the [server], [clean], and [mcp] extras. If you only installed bare netstead and hit ImportError: No module named 's3fs', that’s the missing extra:
2. Provide credentials ¶
Credentials cascade in a fixed order. The first one resolved wins:
- Explicit kwarg —
Network.from_source(url, credential="…"). Always wins. Use for one-off scripts and notebooks. - Environment variable —
CORRAL_CRED_<HOST>_TOKENwhere<HOST>is the URL host uppercased with dots and dashes flattened to underscores. Fors3://my-bucket/...that’sCORRAL_CRED_MY_BUCKET_TOKEN. Use for CI. - System keyring — service name
corral, username matches host. Use for local dev so a token never lands in your shell history. ~/.netrc— falls back to the standard netrc machine entry. Use for legacy compatibility.
For AWS specifically, the default boto credential chain runs inside step 1 — IAM role, ~/.aws/credentials, AWS_* env vars. You only need a CORRAL_CRED_* variable when the bucket needs a non-default credential (e.g. a different AWS account, a custom MinIO instance with HTTP Basic).
To store a credential in the keyring on a dev machine — once, interactively — call the keyring API directly:
The same call from python -c works for CI bootstrapping if you’d rather not put the secret in an env file.
3. Load the package ¶
Discovery via the cascade is the common path. An explicit kwarg overrides discovery, which is handy in notebooks where you’d rather not depend on shell environment:
from netstead import Network
# Discover via the cascade above:
net = Network.from_source("s3://my-bucket/networks/leavenworth/")
# Or pass an explicit credential (kwarg always wins):
net = Network.from_source(
"s3://my-bucket/networks/leavenworth/",
credential={"key": "AKIA…", "secret": "…"},
)
The result is the same Network you’d get from a local path. Every table (net.links, net.nodes, net.lanes, …) is an ibis expression backed by the cloud-aware filesystem.
4. Verify the load is lazy ¶
Inspect the underlying expression and confirm it’s an ibis Table. Then push a filter down — only matching rows transit:
expr = net.links.expr # underlying ibis Table
print(type(expr).__name__) # 'Table'
fast_links = net.links.filter(net.links.free_speed > 45.0).to_polars()
Filters and column projections push down to the parquet readers, so a 10 GB package over S3 can return a small filtered frame in a few seconds without downloading the whole file.
5. Cache for repeat reads ¶
If the same script will hit the package many times — a notebook, a debugging loop, a CI matrix — convert it once and load locally afterwards:
from netstead import Network
remote = Network.from_source("s3://my-bucket/networks/leavenworth/")
remote.write("/tmp/leavenworth.parquet") # one-shot download
# Subsequent runs:
net = Network.from_source("/tmp/leavenworth.parquet")
Parquet is faster to re-read than CSV-over-S3 by an order of magnitude on cold cache. See Convert formats for the format trade-offs.
Common variations ¶
S3 (default) — AWS chain or CORRAL_CRED_*_TOKEN
Requires pip install 'netstead[server]' (brings in s3fs). The boto chain handles standard AWS auth automatically. Custom endpoints (MinIO, R2, Wasabi) need an endpoint_url kwarg.
```python
net = Network.from_source(
"s3://my-bucket/networks/leavenworth/",
endpoint_url="https://s3.us-west-2.minio.local",
)
```
HTTPS with bearer token or HTTP Basic
Bearer is detected when the token has no :; pass user:pass for HTTP Basic.
```python
net = Network.from_source(
"https://data.example.org/networks/leavenworth/",
credential="ey...JWT...",
)
```
Azure Blob Storage (az://)
Requires adlfs from the [server] extra. Credential can be an account key, SAS token, or default Azure credential chain.
```python
net = Network.from_source("az://container@account/networks/leavenworth/")
```
Google Cloud Storage (gs://)
Requires gcsfs from the [server] extra. Set GOOGLE_APPLICATION_CREDENTIALS to the path of your service-account JSON file.
```python
net = Network.from_source("gs://my-bucket/networks/leavenworth/")
```
DuckDB over HTTP
Single-file DuckDB databases read directly over HTTPS via the native httpfs reader — one round-trip per table.
```python
net = Network.from_source("duckdb://https://data.example.org/leavenworth.duckdb")
```
Force anonymous reads on a public bucket
The AWS chain still attempts to sign requests if any credential is present in the environment. Force unsigned access:
```python
net = Network.from_source(
"s3://public-open-data/networks/leavenworth/",
credential={"anon": True},
)
```
Pitfalls ¶
- Credential precedence is fixed and not configurable. A kwarg always overrides env / keyring / netrc. If your CI keeps reading the wrong credential, check whether something upstream is passing
credential=tofrom_source. - Regional endpoints matter. For non-default AWS regions or S3-compatible stores (MinIO, R2, Wasabi), set
endpoint_urlvia a kwarg orAWS_ENDPOINT_URLenv var — the cascade doesn’t auto-discover non-AWS endpoints. - Pre-signed URLs have TTLs. If you pass an
https://…?X-Amz-Signature=…URL, the credential is baked in and the URL expires. Re-issue for long-running jobs. - Listing a bucket isn’t free.
Package.from_source("s3://bucket/")(no prefix) walks the bucket. Always include the package directory prefix. - Anonymous reads need an explicit signal. For public buckets the AWS chain still attempts to sign requests if any credential is present in the environment. Pass
credential={"anon": True}to force unsigned access. - Keyring on Linux needs a backend. The Python
keyringlibrary on a headless Linux box falls through tofail.Keyringif no backend is installed. Installkeyrings.altfor a file-backed store, or use the env-var path instead.
See also ¶
- Architecture — package/network/engine layering and the
from_sourcedispatcher. - Convert formats — once loaded, write the package out as parquet or duckdb for faster repeat loads.
- API reference —
Network.from_source,Package.from_source.