Welcome to the new DOCS.vastdata.com. We've added new menus and a version selector, plus new persona and resource pages to help customers get started. Looking for a previous article? Use the docs search.

Python clients

Prev Next

Applies to VAST Cluster 5.5.

Any Python library that speaks S3 works with VAST S3 if you give it the cluster's endpoint and your key pair. This page shows the settings for the three most used: boto3 (the AWS SDK), s3fs (the fsspec filesystem that pandas and Dask use for s3:// paths) and PyArrow's S3FileSystem (for Parquet and datasets). The examples use the prefix docs-python/, and the last block removes it.

Each code block is complete on its own. Below each block is the output you should see.

Before you start

You need the bucket, key pair and environment variables from the quickstart, and the libraries:

pip install "boto3>=1.36" s3fs pyarrow

boto3

These are the settings that matter for VAST:

Setting Value Why
endpoint_url http://<name or VIP> or https://... The cluster's S3 endpoint. Without it boto3 goes to AWS. No port: the cluster listens on 80 and 443.
region_name Any string, for example us-east-1 Signature Version 4 needs a region; the cluster accepts any.
signature_version s3v4 The recommended signature. Version 2 (s3) also works.
s3={"addressing_style": "path"} Path-style URLs (endpoint/bucket/key) The addressing style to use with VAST. Bucket-in-hostname (virtual-hosted) URLs aren't documented.
max_pool_connections At least your number of threads See performance.
import os

import boto3
from botocore.config import Config

s3 = boto3.client(
    "s3",
    endpoint_url=os.environ["AWS_S3_ENDPOINT_URL"],
    region_name="us-east-1",
    config=Config(
        signature_version="s3v4",
        s3={"addressing_style": "path"},
        max_pool_connections=16,
        retries={"max_attempts": 5, "mode": "standard"},
    ),
)
bucket = os.environ["DATASTORE_BUCKET"]

s3.put_object(Bucket=bucket, Key="docs-python/boto3.txt", Body=b"from boto3\n")
print(s3.meta.config.signature_version, s3.meta.config.s3)
print(s3.get_object(Bucket=bucket, Key="docs-python/boto3.txt")["Body"].read())
s3v4 {'addressing_style': 'path'}
b'from boto3\n'

The higher-level boto3.resource("s3", ...) takes the same arguments.

Recent boto3 and botocore releases can also take the endpoint from the environment variable AWS_ENDPOINT_URL_S3 or from endpoint_url in ~/.aws/config, which is handy for code you don't want to change. Passing endpoint_url explicitly works with every version.

HTTPS and certificates

The cluster comes with a self-signed certificate for S3, which your platform team may have replaced with one signed by your CA. If your system doesn't trust the certificate, point the client at a file that contains it (or its CA) rather than turning verification off:

s3 = boto3.client("s3", endpoint_url="https://s3.example.com", verify="/path/to/vast-s3-ca.pem",
                  region_name="us-east-1", config=Config(signature_version="s3v4", s3={"addressing_style": "path"}))

Setting AWS_CA_BUNDLE in the environment does the same for every boto3 client. If the certificate's host name doesn't match the name you connect to, use a name that's in the certificate. verify=False turns checking off; use it only on a test system.

s3fs (pandas, Dask and fsspec)

s3fs gives s3:// paths a file-like interface. Give it the endpoint and path-style addressing; it reads the keys from the same environment variables as boto3:

import os

import s3fs

fs = s3fs.S3FileSystem(
    endpoint_url=os.environ["AWS_S3_ENDPOINT_URL"],
    config_kwargs={"s3": {"addressing_style": "path"}, "signature_version": "s3v4"},
    client_kwargs={"region_name": "us-east-1"},
)
bucket = os.environ["DATASTORE_BUCKET"]

with fs.open(f"{bucket}/docs-python/s3fs/hello.txt", "w") as f:
    f.write("from s3fs\n")
print(fs.ls(f"{bucket}/docs-python/s3fs/"))
print(fs.cat(f"{bucket}/docs-python/s3fs/hello.txt"))
['dst-data/docs-python/s3fs/hello.txt']
b'from s3fs\n'

pandas uses s3fs for s3:// URLs, so pass the same options as storage_options:

import pandas as pd

df = pd.read_csv(
    f"s3://{bucket}/path/to/file.csv",
    storage_options={
        "endpoint_url": os.environ["AWS_S3_ENDPOINT_URL"],
        "config_kwargs": {"s3": {"addressing_style": "path"}},
    },
)

PyArrow

pyarrow.fs.S3FileSystem is Arrow's own S3 client, used by pyarrow.parquet and pyarrow.dataset. It takes the endpoint as a host name (and optional port) plus a scheme, and uses path-style addressing when you give it an endpoint. Write a Parquet file and read it back:

import os
from urllib.parse import urlparse

import pyarrow as pa
import pyarrow.parquet as pq
from pyarrow import fs

endpoint = urlparse(os.environ["AWS_S3_ENDPOINT_URL"])
s3 = fs.S3FileSystem(
    access_key=os.environ["AWS_ACCESS_KEY_ID"],
    secret_key=os.environ["AWS_SECRET_ACCESS_KEY"],
    endpoint_override=endpoint.netloc,
    scheme=endpoint.scheme,
    region="us-east-1",
)
bucket = os.environ["DATASTORE_BUCKET"]

table = pa.table({"id": [1, 2, 3], "city": ["Paris", "Lima", "Osaka"]})
for part in range(2):
    pq.write_table(table, f"{bucket}/docs-python/parquet/part-{part}.parquet", filesystem=s3)

dataset = pq.ParquetDataset(f"{bucket}/docs-python/parquet/", filesystem=s3)
result = dataset.read()
print(result.num_rows, "rows from", len(dataset.files), "files")
print(result.schema)
6 rows from 2 files
id: int64
city: string

Readers fetch Parquet footers and column chunks with ranged GETs, so reading a few columns of a large file reads only those bytes. To load Parquet files into a VAST DataBase table without reading them through your client, use the cluster-side import: Import Parquet files.

Clean up

import os

import boto3
from botocore.config import Config

s3 = boto3.client(
    "s3",
    endpoint_url=os.environ["AWS_S3_ENDPOINT_URL"],
    region_name="us-east-1",
    config=Config(signature_version="s3v4", s3={"addressing_style": "path"}),
)
bucket = os.environ["DATASTORE_BUCKET"]

keys = [{"Key": o["Key"]} for o in s3.list_objects_v2(Bucket=bucket, Prefix="docs-python/").get("Contents", [])]
if keys:
    s3.delete_objects(Bucket=bucket, Delete={"Objects": keys})
print("deleted", len(keys), "objects")
deleted 4 objects

See also