Skip to main content

StashKat

stashkat provides a pluggable S3 storage backend for corekat. It implements the Fetcher protocol, allowing corekat's DownloadClient to handle s3:// URLs. It also provides a CLI for direct S3 operations.

Features

  • S3 Fetcher for CoreKat: Enables corekat.client.filedl.DownloadClient to download from S3.
  • Async-first S3 Client: A high-level, asynchronous client for interacting with S3-compatible object storage.
  • Command-Line Interface: stashkat CLI for uploading, downloading, and copying files from S3.

Installation

uv pip install stashkat

Integration with CoreKat

When stashkat is installed in the same environment as corekat, its S3 fetcher is automatically registered upon initialization.

Programmatic Usage with DownloadClient

import asyncio
from corekat.config import config
from corekat.client.filedl import DownloadClient
from stashkat.init import init as stashkat_init

async def main():
    # Load config (which should include S3 settings)
    conf = config()
    # Initialize stashkat to register S3 fetcher
    stashkat_init(conf)

    downloader = DownloadClient()

    # Download from S3 using corekat's DownloadClient
    file_info = await downloader.download(
        source="s3://my-bucket/my-file.txt",
        dest_dir="/tmp"
    )
    print(f"Downloaded {file_info.path}")

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

Direct S3Client Usage

import asyncio
from stashkat.config import config
from stashkat.s3 import S3Client
from stashkat.models import StoragePath

async def main():
    conf = config()
    client = S3Client(conf.s3)
    
    # Upload file
    storage_path = await client.upload_file("local/file.txt", dest="uploads/file.txt")
    print(f"Uploaded to: {storage_path.url}")
    
    # Download file
    remote = StoragePath.model_validate("s3://my-bucket/uploads/file.txt")
    await client.download_file(remote, dest="/tmp/downloaded.txt")
    
    # List files
    files = await client.list(prefix="uploads/")
    for file in files:
        print(f"Found: {file.url}")

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

Errors & retries

Every backend translates its provider-specific failures into a small, portable exception hierarchy (in stashkat.exceptions), so you catch the same types regardless of whether S3, the filesystem, or a future backend is behind the StashStorage interface. All inherit corekat's AppException, so an error that reaches a servekat route maps to a sensible HTTP status automatically.

Exception Raised when HTTP retryable
StashError base class / uncategorised failure 500 False
StashConfigError misconfig, missing/partial creds, scheme mismatch (also a ValueError) 500 False
StashNotFoundError object / key / bucket does not exist 404 False
StashAuthError bad credentials / access denied 403 False
StashTransientError throttling, 5xx, connection/timeout — safe to retry 503 True
from stashkat import StashNotFoundError, StashTransientError, get_storage

storage = get_storage(conf, scheme="s3")
try:
    await storage.download_file(remote, dest="/tmp/out.txt")
except StashNotFoundError:
    ...  # 404 to the user — the object isn't there
except StashTransientError as e:
    assert e.retryable  # back off and retry

Branch on err.retryable to drive backoff/retry without importing botocore or knowing which backend produced the error. Credentials are never validated eagerly at client construction — an empty credential pair is a valid configuration (resolved from the environment / IAM role / instance-profile chain), and a broken pair surfaces lazily as a StashConfigError on first use.

retry_on_transient wraps that contract in a ready-made backoff loop — it retries only StashTransientError and re-raises everything else immediately:

from stashkat import retry_on_transient

stat = await retry_on_transient(lambda: storage.stat(remote), attempts=3)

Metadata, existence & presigned URLs

Beyond the read/write/copy basics, every backend exposes the operations apps otherwise drop down to raw boto3 for:

storage = get_storage(conf, scheme="s3")

# Cheap existence check + metadata (no download):
if await storage.exists(remote):
    info = await storage.stat(remote)      # -> ObjectStat
    print(info.size, info.content_type, info.etag, info.last_modified)

# Record a content type on upload (served back by stat / on GET):
await storage.upload_file(file_obj, dest="docs/report.pdf", content_type="application/pdf")

# Presigned URLs — let the browser talk to object storage directly (S3 only):
get_url = await storage.presigned_get_url(remote, expires_in=300)
put = await storage.presigned_put_url(dest, content_type="image/png", expires_in=300)

presigned_get_url / presigned_put_url are pure local SigV4 crypto (no network I/O). The filesystem backend has no presign concept, so it raises StashConfigError; exists / stat work on both backends (the filesystem backend guesses content_type from the extension and has no etag).

For synchronous, high-volume signing (e.g. a sync view-builder that signs many URLs per request), use S3Presigner — a plain sync class that reuses one botocore client instead of building an async client per call:

from stashkat import S3Presigner

presigner = S3Presigner(conf.s3)          # built from the same S3ConfigSchema
if presigner.available:                   # explicit credentials present?
    get_url = presigner.get_url("s3://bucket/key.jpg", expires_in=300)
    put_url = presigner.put_url("uploads/x.png", content_type="image/png")

CLI Usage

Configuration

The CLI requires S3 configuration to be present in a config.yaml or localconfig.yaml file, or via environment variables (prefixed with STASHKAT_).

Example config.yaml:

s3:
  bucket: "my-default-bucket"
  region: "us-east-1"
  access_key: "..." # Or use environment variables
  secret_key: "..." # Or use environment variables

Commands

Upload a file:

stashkat s3 upload /path/to/local/file.txt

Upload with a specific destination key:

stashkat s3 upload /path/to/local/file.txt --dest "archive/file.txt"

Download a file:

stashkat s3 download s3://my-bucket/archive/file.txt /path/to/save/location.txt

Copy a file:

stashkat s3 copy s3://my-bucket/source.txt s3://my-bucket/destination.txt

Release files for stashkat 0.1.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for stashkat 0.1.5
File Size Uploaded
stashkat-0.1.5.tar.gz 35.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for stashkat 0.1.5
File Interpreter ABI Platform
stashkat-0.1.5-py3-none-any.whl Python 3 none any Details

Total release size: 71.8 kB

Release files / stashkat-0.1.5.tar.gz

Download URL stashkat-0.1.5.tar.gz
Size 35.0 kB
Tags Source
SHA-256 checksum
How to use checksums
bc0a849b395de664197fbfb99a1def9fe0b9e38ece8f444cabc690fe4844cca5
BLAKE2b-256 checksum
How to use checksums
4ead6c880a4341e414246673b3e9967db2407fa510f27b601fbcbf2b732745e7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","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":null}

Release files / stashkat-0.1.5-py3-none-any.whl

Download URL stashkat-0.1.5-py3-none-any.whl
Size 36.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
16ddfb0cd8b3c754c9d5c430cbe2da4bc1b9592dcc09ac3b5cd31669d4abde87
BLAKE2b-256 checksum
How to use checksums
7943b98dfe5d7c334a66a6b2b42264eb2a9fae09f02c1ea30b7cecd104857e2a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","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":null}

Release history Release notifications | RSS feed

This release

0.1.5 This release

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page