Skip to main content

Unified object storage client API

Project description

Object Storage Client

A unified object storage client for Rust and Python, supporting S3, GCS, Azure Blob Storage, HTTP/HTTPS, and Local Filesystem. It provides a simple, URL-based API for object operations, including cross-provider copy and move.

Features

  • Unified API: Single interface for various storage backends.
  • Cross-Provider: Copy or move objects between different storage providers (e.g., S3 to Local FS).
  • Existence checks: Test whether an object or bucket exists without raising on a miss.
  • Bucket creation: Create buckets/containers on S3, GCS and Azure (or directories for local paths).
  • Pre-signed URLs: Generate time-limited, credential-free URLs for S3, GCS and Azure.
  • Multi-Language: Native Rust library with Python 3.13+ bindings.
  • Streaming: Async streaming support for both Rust and Python.
  • CLI: osc command-line tool for quick operations.

Supported Schemes

  • s3://bucket/path (AWS S3)
  • gs://bucket/path or gcs://bucket/path (Google Cloud Storage)
  • az://, wasb://, wasbs://, abfs://, or abfss:// (Azure Blob Storage)
  • http://host/path or https://host/path (HTTP/HTTPS)
  • file:///absolute/path or local_path (Local Filesystem)

Environment Variables

Credentials are read from the environment the first time a backend is used — the client never takes them as constructor arguments. The bucket / container always comes from the URL host, so a single process can talk to several buckets across several providers at once. That is exactly what makes the cross-provider copy and move shown in the walk-throughs below work: export the variables for every provider you touch, and a single ObjectStorageClient can shuttle objects between them. The local filesystem needs no variables.

AWS S3 (s3://)

Also covers S3-compatible stores such as MinIO and SeaweedFS.

export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_REGION="us-east-1"
# Optional: temporary credentials
export AWS_SESSION_TOKEN="..."
# Optional: custom endpoint for S3-compatible stores (e.g. MinIO)
export AWS_ENDPOINT="http://localhost:9000"

# Convenience overrides honoured by this client (take precedence when set):
#   S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY
# Allow plain HTTP (e.g. a local MinIO):
#   export S3_SECURE=false

Google Cloud Storage (gs:// / gcs://)

# Path to a service-account JSON key file...
export GOOGLE_SERVICE_ACCOUNT="/path/to/service-account.json"
# (GOOGLE_SERVICE_ACCOUNT_PATH and the standard
#  GOOGLE_APPLICATION_CREDENTIALS are also recognised.)

# ...or the service-account JSON supplied inline instead of a path:
#   export GOOGLE_SERVICE_ACCOUNT_KEY='{"type":"service_account", ...}'

Azure Blob Storage (az://, wasb(s)://, abfs(s)://)

export AZURE_STORAGE_ACCOUNT_NAME="mystorageaccount"

# Pick ONE authentication method:
# 1. Shared account key
export AZURE_STORAGE_ACCOUNT_KEY="..."
# 2. Shared Access Signature (SAS) token
#   export AZURE_STORAGE_SAS_KEY="?sv=..."
# 3. Service principal (Azure AD)
#   export AZURE_STORAGE_CLIENT_ID="..."
#   export AZURE_STORAGE_CLIENT_SECRET="..."
#   export AZURE_STORAGE_TENANT_ID="..."

Local Filesystem (file://)

No environment variables — no credentials are required.


CLI Usage (osc)

The osc tool allows you to interact with object storage directly from your terminal.

Installation

Install the osc binary directly from the Bixority Codeberg crate registry with Cargo. Point Cargo at the registry with an environment variable, then install:

export CARGO_REGISTRIES_BIXORITY_INDEX="sparse+https://codeberg.org/api/packages/bixority/cargo/"
cargo install object-storage-client --registry bixority

Alternatively, install straight from Git:

cargo install --git https://codeberg.org/bixority/object-storage-client

Or, if you have the source code, install it from the local checkout:

cargo install --path .

Examples

  • Upload a local file:

    osc put my_file.txt s3://my-bucket/remote_file.txt
    
  • Download an object:

    osc get gs://my-bucket/data.json ./local_data.json
    
  • Copy between providers:

    osc cp s3://source-bucket/image.png az://dest-container/image.png
    
  • Move an object:

    osc mv s3://my-bucket/old_name.txt s3://my-bucket/new_name.txt
    
  • List objects:

    osc ls s3://my-bucket/logs/
    
  • Delete an object:

    osc rm s3://my-bucket/temp_file.tmp
    
  • Check whether an object exists (prints true/false):

    osc exists s3://my-bucket/report.pdf
    
  • Create a bucket (S3, GCS, Azure, or a directory for local paths):

    osc mb s3://my-new-bucket
    
  • Check whether a bucket exists (prints true/false):

    osc bucket-exists s3://my-bucket
    
  • Stream an object:

    osc get-stream gs://my-bucket/large_file.bin
    
  • Generate a pre-signed URL (S3, GCS, Azure):

    # Pre-signed download URL, valid for the default 1 hour
    osc sign s3://my-bucket/report.pdf
    
    # Pre-signed upload URL (PUT), valid for 15 minutes
    osc sign --method PUT --expires-in 900 s3://my-bucket/upload.bin
    
    # Pre-signed upload URL binding the exact size and type the client must send
    # (S3 only): the upload is rejected unless Content-Length and Content-Type
    # match, so the object store enforces size/type up front.
    osc sign --method PUT --content-length 1048576 \
        --content-type application/pdf s3://my-bucket/upload.pdf
    

Rust Usage

Installation

The crate is published to the Bixority Codeberg crate registry. Point Cargo at the registry with an environment variable:

export CARGO_REGISTRIES_BIXORITY_INDEX="sparse+https://codeberg.org/api/packages/bixority/cargo/"

Then add object-storage-client to your Cargo.toml:

[dependencies]
object-storage-client = { version = "0.0.33", registry = "bixority" }
tokio = { version = "1.0", features = ["full"] }

Alternatively, you can depend on it directly from Git:

[dependencies]
object-storage-client = { git = "https://codeberg.org/bixority/object-storage-client" }
tokio = { version = "1.0", features = ["full"] }

Walk-through

A single client works across every provider — the scheme in each URL selects the backend, so you can upload to S3, then copy or move the object straight to GCS, Azure or the local disk with no intermediate download on your side.

use object_storage_client::{ObjectStorageClient, SignMethod, SignOptions};
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = ObjectStorageClient::new();

    // Create a bucket (S3/GCS/Azure, or a directory for file:// URLs); idempotent
    client.create_bucket("s3://my-bucket").await?;

    // Upload data to S3
    client.put("s3://my-bucket/hello.txt", &b"Hello from Rust!"[..]).await?;

    // Download it back
    let retrieved = client.get("s3://my-bucket/hello.txt").await?;
    println!("Retrieved: {}", String::from_utf8_lossy(&retrieved));

    // Existence checks (missing -> Ok(false), never an error)
    if client.bucket_exists("s3://my-bucket").await? {
        println!("my-bucket is present");
    }
    if client.exists("s3://my-bucket/hello.txt").await? {
        println!("hello.txt is present");
    }

    // --- Move data across providers with one client ---

    // Copy S3 -> Google Cloud Storage (source is left in place)
    client
        .copy("s3://my-bucket/hello.txt", "gs://my-gcs-bucket/hello.txt")
        .await?;

    // Move GCS -> Azure Blob Storage (source is deleted afterwards)
    client
        .move_object("gs://my-gcs-bucket/hello.txt", "az://my-container/hello.txt")
        .await?;

    // Copy Azure -> local disk for a working copy
    client
        .copy("az://my-container/hello.txt", "file:///tmp/hello_local.txt")
        .await?;

    // --- Pre-signed URLs: time-limited, credential-free access (S3/GCS/Azure) ---

    // Pre-signed download (GET) link, valid for one hour
    let download_url = client
        .get_pre_signed_url(
            "s3://my-bucket/hello.txt",
            SignMethod::Get,
            Duration::from_secs(3600),
            &SignOptions::default(),
        )
        .await?;
    println!("Share this download link: {download_url}");

    // Pre-signed upload (PUT) link binding the exact size and type the client
    // must send (S3 only); the store rejects mismatched uploads up front.
    let upload_url = client
        .get_pre_signed_url(
            "s3://my-bucket/upload.bin",
            SignMethod::Put,
            Duration::from_secs(900),
            &SignOptions {
                content_length: Some(1_048_576),
                content_type: Some("application/octet-stream".to_string()),
            },
        )
        .await?;
    println!("Upload directly to: {upload_url}");

    Ok(())
}

Python 3.13+ Usage

Installation

The package is published on PyPI. Note that it requires Python 3.13+.

pip install object-storage-client

Or if you are developing locally, you can use maturin:

maturin develop

Walk-through

The same client handles every provider; the scheme in each URL picks the backend, so copying or moving an object between S3, GCS, Azure and local disk is a single call.

import asyncio
from object_storage_client import ObjectStorageClient

async def main():
    client = ObjectStorageClient()

    # Create a bucket (S3/GCS/Azure, or a directory for file:// URLs); idempotent
    await client.create_bucket("s3://my-bucket")

    # Check whether a bucket exists (returns a bool; never raises for a miss).
    if await client.bucket_exists("s3://my-bucket"):
        print("my-bucket is present")

    # Upload data to S3
    await client.put_object("s3://my-bucket/hello.txt", b"Hello from Python!")

    # Check whether an object exists (returns a bool; never raises for a miss).
    # If you prefer the missing case to raise FileNotFoundError, use
    # get_object_metadata() or get_object() instead.
    if await client.object_exists("s3://my-bucket/hello.txt"):
        print("hello.txt is present")

    # Fetch full metadata (raises FileNotFoundError if the object is missing)
    meta = await client.get_object_metadata("s3://my-bucket/hello.txt")
    print(f"Size: {meta['size_bytes']}, type: {meta['content_type']}")

    # Download data
    data = await client.get_object("s3://my-bucket/hello.txt")
    print(f"Retrieved: {data.decode()}")

    # List objects
    items = await client.list_objects("s3://my-bucket/")
    print(f"Bucket items: {items}")

    # Stream data
    stream = await client.get_object_stream("s3://my-bucket/hello.txt")
    async for chunk in stream:
        print(f"Chunk size: {len(chunk)}")

    # --- Move data across providers with one client ---

    # Copy S3 -> Google Cloud Storage (source is left in place)
    await client.copy_object("s3://my-bucket/hello.txt", "gs://my-gcs-bucket/hello.txt")

    # Move GCS -> Azure Blob Storage (source is deleted afterwards)
    await client.move_object("gs://my-gcs-bucket/hello.txt", "az://my-container/hello.txt")

    # Copy Azure -> local disk for a working copy
    await client.copy_object("az://my-container/hello.txt", "file:///tmp/hello_local.txt")

    # --- Pre-signed URLs (S3/GCS/Azure): credential-free, time-limited access ---

    download_url = await client.get_pre_signed_url("s3://my-bucket/hello.txt")
    # Bind the exact Content-Length and Content-Type the client must send (S3
    # only); the store rejects uploads that don't match.
    upload_url = await client.get_pre_signed_url(
        "s3://my-bucket/upload.bin",
        method="PUT",
        expires_in_secs=900,
        content_length=1_048_576,
        content_type="application/octet-stream",
    )
    print(f"Download: {download_url}\nUpload: {upload_url}")

if __name__ == "__main__":
    asyncio.run(main())

Developer Instructions

Prerequisites

  • Rust 1.85+ (or latest stable)
  • Python 3.13+
  • maturin (for Python bindings)

Building

  • Rust: cargo build --release
  • Python: maturin build --release
  • CLI: cargo build --bin osc

Testing

cargo test

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

object_storage_client-0.0.34.tar.gz (66.6 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

object_storage_client-0.0.34-cp314-cp314-win_amd64.whl (3.3 MB view details)

Uploaded CPython 3.14Windows x86-64

object_storage_client-0.0.34-cp314-cp314-manylinux_2_28_aarch64.whl (3.5 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.28+ ARM64

object_storage_client-0.0.34-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.8 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.17+ x86-64

object_storage_client-0.0.34-cp314-cp314-macosx_11_0_arm64.whl (3.3 MB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

object_storage_client-0.0.34-cp313-cp313-win_amd64.whl (3.3 MB view details)

Uploaded CPython 3.13Windows x86-64

object_storage_client-0.0.34-cp313-cp313-manylinux_2_28_aarch64.whl (3.5 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.28+ ARM64

object_storage_client-0.0.34-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.8 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64

object_storage_client-0.0.34-cp313-cp313-macosx_11_0_arm64.whl (3.3 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

File details

Details for the file object_storage_client-0.0.34.tar.gz.

File metadata

  • Download URL: object_storage_client-0.0.34.tar.gz
  • Upload date:
  • Size: 66.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for object_storage_client-0.0.34.tar.gz
Algorithm Hash digest
SHA256 76ec633e3b7fabf977a984f2287aa5e064598ebe0e61f8f91ba8e683a20c7e42
MD5 410e7a6a162b92ec01e9f56cd7e7a712
BLAKE2b-256 3f9f50fa05da1b42d9514fd19c979ef7bf0491a64ceed8d1baf76e89c3eca781

See more details on using hashes here.

File details

Details for the file object_storage_client-0.0.34-cp314-cp314-win_amd64.whl.

File metadata

  • Download URL: object_storage_client-0.0.34-cp314-cp314-win_amd64.whl
  • Upload date:
  • Size: 3.3 MB
  • Tags: CPython 3.14, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for object_storage_client-0.0.34-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 17be983131db9f2dee1c68ac16f1155754b5ab46ba960344ae4d91378fd63907
MD5 ee2cedb75a7e05848e9fd9c66d4c474f
BLAKE2b-256 41eb48e9b3a81ece1a16db900200aa3c6362e387a3090b4a9ac4d8e16a832740

See more details on using hashes here.

File details

Details for the file object_storage_client-0.0.34-cp314-cp314-manylinux_2_28_aarch64.whl.

File metadata

  • Download URL: object_storage_client-0.0.34-cp314-cp314-manylinux_2_28_aarch64.whl
  • Upload date:
  • Size: 3.5 MB
  • Tags: CPython 3.14, manylinux: glibc 2.28+ ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for object_storage_client-0.0.34-cp314-cp314-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 4a1006aa3b2e6cfe17c2988f41db4daea28dcf7ccd6e90175aebe6f9ff0dc9e0
MD5 b9b8ab62f2f83ed55d96d77ca5e7eceb
BLAKE2b-256 f46e610107a3f2ecf9bbc40bd5d15d0d2ddcf88f8b6dd964eebf0a6268e90a1d

See more details on using hashes here.

File details

Details for the file object_storage_client-0.0.34-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

  • Download URL: object_storage_client-0.0.34-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
  • Upload date:
  • Size: 3.8 MB
  • Tags: CPython 3.14, manylinux: glibc 2.17+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for object_storage_client-0.0.34-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 bcb5b6a4fd15231b149b5105f431455d547ac95444d12d376abe9344fd77143a
MD5 fcb4804fabffecd5d84a54e6d3edbc19
BLAKE2b-256 13304d1ef080b226e7114a034bd9a6b291a07fa6e04589a52f4062b0836e06e2

See more details on using hashes here.

File details

Details for the file object_storage_client-0.0.34-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

  • Download URL: object_storage_client-0.0.34-cp314-cp314-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 3.3 MB
  • Tags: CPython 3.14, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for object_storage_client-0.0.34-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 cc0444e2116111c23760a95a56951a43e2cf1d7a197d6e908561dde3b87aac5f
MD5 3ee96b368f56a25be5e285229d334893
BLAKE2b-256 ea9288da433009c2800f48ceb21265356b7e24bfbdc874cd3af4a8d7efb043b1

See more details on using hashes here.

File details

Details for the file object_storage_client-0.0.34-cp313-cp313-win_amd64.whl.

File metadata

  • Download URL: object_storage_client-0.0.34-cp313-cp313-win_amd64.whl
  • Upload date:
  • Size: 3.3 MB
  • Tags: CPython 3.13, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for object_storage_client-0.0.34-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 ac5b3fe5b150e6a967fd83bbee23191b054fd934716e575f52d58deda5a7dfd7
MD5 b48866935c4e33c9d47fe8a046665e46
BLAKE2b-256 f4b58b167cfcda26b8b15b2e8feca4d01aae6ebd4890abbc86b0de10b566ca55

See more details on using hashes here.

File details

Details for the file object_storage_client-0.0.34-cp313-cp313-manylinux_2_28_aarch64.whl.

File metadata

  • Download URL: object_storage_client-0.0.34-cp313-cp313-manylinux_2_28_aarch64.whl
  • Upload date:
  • Size: 3.5 MB
  • Tags: CPython 3.13, manylinux: glibc 2.28+ ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for object_storage_client-0.0.34-cp313-cp313-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 e7519ca8c410a07b95fa5587e12ccd3c516d9166203c7ec06c0ab9b08067d79f
MD5 1351a8d61bd667747139f8b9c40dbca9
BLAKE2b-256 edbdbeb6595d0cf985679fed65ba84641e244ba26eea6d6760004c49b081373f

See more details on using hashes here.

File details

Details for the file object_storage_client-0.0.34-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

  • Download URL: object_storage_client-0.0.34-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
  • Upload date:
  • Size: 3.8 MB
  • Tags: CPython 3.13, manylinux: glibc 2.17+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for object_storage_client-0.0.34-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 804a85fecc9a34971d71cb99a16921ca7e4279afc9de3490f15b8420a8ed6a6f
MD5 471261716a094615be2cd2918ce5b562
BLAKE2b-256 9e0c7ea5b8179c8c3442fc92c44a5b7af44c266d9e6b9ec83ca1459a36d17c0c

See more details on using hashes here.

File details

Details for the file object_storage_client-0.0.34-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

  • Download URL: object_storage_client-0.0.34-cp313-cp313-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 3.3 MB
  • Tags: CPython 3.13, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for object_storage_client-0.0.34-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 5087981a4d0a82e0e458811a9e6138f0f47de8a7a6b137d57f2f79e5377be575
MD5 8d0478e8b1450cdad037f890162661b3
BLAKE2b-256 85ed295aaa2c6b8a3b420b6394b0cd6a92168a8e09e4d6b84a9c33a5548b1a9f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page