Skip to main content

A Python library for uploading workflow artifacts to Amazon S3 using STS credentials.

Project description

vcm-file-uploader

A Python library for uploading workflow artifacts to Amazon S3 using temporary STS credentials.

Features

  • STS credential sessions — scoped uploads using temporary AWS credentials with prefix enforcement
  • boto3 TransferManager — configurable multipart thresholds, chunk sizes, and concurrency
  • Automatic retries — exponential backoff with adaptive retry mode for transient S3 failures
  • ASCII log compression — gzip compression for large text files with stability detection
  • Growing log segmentation — incremental byte-range uploads for actively growing log files
  • JSONL state management — append-only, last-write-wins state tracking with compaction
  • Manifest generation — JSON manifests recording all uploaded objects with metadata
  • File monitoring — directory scanning, diffing, and upload plan generation via BackupWatchman
  • Partial failure reporting — structured exit codes (0/1/2) for programmatic callers

Installation

pip install vcm-file-uploader

With development dependencies:

pip install "vcm-file-uploader[dev]"

Requires Python 3.10+.

Quick Start

from vcm_file_uploader import STSUploadSession, UploadPlan, ManifestWriter

# Load an upload plan produced by BackupWatchman
plan = UploadPlan.from_json("upload_plan.json")

# Create a session with STS credentials and upload
with STSUploadSession(
    aws_access_key_id="AKIA...",
    aws_secret_access_key="secret",
    aws_session_token="token",
    region="us-east-1",
    bucket="my-bucket",
    prefix="jobs/12345/",
) as session:
    summary = session.upload_files(plan)

    # Write a manifest recording what was uploaded
    manifest = ManifestWriter(session)
    manifest.add_summary(summary)
    manifest.write_manifest()

# Check results programmatically
print(f"Uploaded {len(summary.succeeded)} files ({summary.total_bytes} bytes)")
sys.exit(summary.exit_code)

Usage Examples

Basic Upload with UploadPlan

from vcm_file_uploader import STSUploadSession, UploadPlan

# Load from JSON file
plan = UploadPlan.from_json("/path/to/upload_plan.json")

# Or construct from a dictionary
plan = UploadPlan.from_dict({
    "files": [
        {"path": "output/results.csv", "full_path": "/data/output/results.csv", "size": 1048576},
        {"path": "logs/run.log", "full_path": "/data/logs/run.log", "size": 524288},
    ]
})

print(f"{len(plan)} files, {plan.total_bytes} bytes, {plan.multipart_count} multipart")

with STSUploadSession(
    aws_access_key_id="AKIA...",
    aws_secret_access_key="secret",
    aws_session_token="token",
    region="us-east-1",
    bucket="my-bucket",
    prefix="jobs/12345/",
) as session:
    summary = session.upload_files(plan)

    if summary.all_succeeded:
        print("All uploads succeeded")
    else:
        for r in summary.failed:
            print(f"FAILED: {r.local_path}: {r.error}")

File Monitoring with BackupWatchman

from vcm_file_uploader import BackupWatchman, UploadPlan

watchman = BackupWatchman(
    watch_dir="/data/workspace",
    output_dir="/data/state",
    multipart_threshold=50 * 1024 * 1024,  # 50 MB
)

# Run a scan-diff-plan cycle
plan_dict = watchman.scan()

# The result can be loaded as an UploadPlan
plan = UploadPlan.from_dict(plan_dict)
print(f"Found {len(plan)} files to upload")

Log Compression

from vcm_file_uploader import maybe_compress, compress_file

# Conditionally compress large ASCII files (>= 64 MiB by default)
result = maybe_compress("/data/logs/simulation.log")
if result:
    compressed_path, info = result
    print(f"Compressed {info.original_bytes} -> {info.compressed_bytes} bytes")

# Force compression with custom settings
compressed_path, info = compress_file(
    "/data/logs/output.log",
    level=9,
    output_path="/tmp/output.log.gz",
)

# Compress log-category files (skips ASCII detection)
result = maybe_compress("/data/logs/binary.log", category="log", threshold=32 * 1024 * 1024)

Growing Log Segmentation

from vcm_file_uploader import LogSegmenter, JsonlStore

store = JsonlStore("/data/state/ascii_segments.jsonl", key_field="segment_key")
segmenter = LogSegmenter(store)

# Detect new bytes in a growing log file
segments = segmenter.get_new_segments("/data/logs/amber.mdout")

for segment in segments:
    print(f"Segment {segment.segment_index}: offset={segment.offset}, length={segment.length}")
    # Upload each segment
    s3_key = f"jobs/12345/{segment.segment_key_suffix}"
    result = segmenter.upload_segment(segment, session, s3_key)

Manifest Generation

from vcm_file_uploader import ManifestWriter

manifest = ManifestWriter(session, run_id="abc123")

# Add results from an upload batch
manifest.add_summary(summary)

# Or add individual results
manifest.add_result(single_result)

# Upload the manifest JSON to S3
manifest.write_manifest()

# Inspect the manifest contents
print(manifest.to_dict())

JSONL State Management

from vcm_file_uploader import StateManager, JsonlStore

# High-level: StateManager provides pre-configured stores
state = StateManager("/data/state")
state.files.append({"path": "output.csv", "uploaded": True, "size": 1024})
state.segments.append({"segment_key": "log.seg0001", "offset": 4096})

entry = state.files.get("output.csv")
all_files = state.files.read_all()

# Compact all stores (deduplicates on disk)
counts = state.compact_all()

# Low-level: use JsonlStore directly
store = JsonlStore("/data/state/custom.jsonl", key_field="id")
store.append({"id": "item-1", "status": "done"})
store.append({"id": "item-1", "status": "updated"})  # overwrites on read
latest = store.get("item-1")  # {"id": "item-1", "status": "updated"}
store.compact()  # deduplicates the file on disk

Configuration

TransferConfig

Controls boto3 S3 TransferManager behavior:

Field Default Description
multipart_threshold 128 MiB File size threshold for multipart uploads
multipart_chunksize 128 MiB Size of each multipart chunk
max_concurrency 8 Maximum concurrent upload threads
from vcm_file_uploader import TransferConfig, STSUploadSession

config = TransferConfig(
    multipart_threshold=64 * 1024 * 1024,
    multipart_chunksize=64 * 1024 * 1024,
    max_concurrency=4,
)

session = STSUploadSession(..., transfer_config=config)

Retry Configuration

Parameter Default Description
max_retries 5 Maximum retry attempts for transient S3 errors
retry_mode "adaptive" boto3 retry mode ("adaptive", "standard", or "legacy")

Compression Defaults

Constant Value Description
DEFAULT_COMPRESS_THRESHOLD 64 MiB Minimum file size for automatic compression
DEFAULT_GZIP_LEVEL 6 Gzip compression level (0-9)
STABILITY_WAIT 2.0 s Wait time between file size checks

API Reference

See docs/api.md for the complete API reference.

Development

# Install in editable mode with dev dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Run linter
ruff check .

# Run type checker
mypy src/

License

MIT — see LICENSE for details.

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

vcm_file_uploader-0.1.0.tar.gz (30.5 kB view details)

Uploaded Source

Built Distribution

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

vcm_file_uploader-0.1.0-py3-none-any.whl (17.3 kB view details)

Uploaded Python 3

File details

Details for the file vcm_file_uploader-0.1.0.tar.gz.

File metadata

  • Download URL: vcm_file_uploader-0.1.0.tar.gz
  • Upload date:
  • Size: 30.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for vcm_file_uploader-0.1.0.tar.gz
Algorithm Hash digest
SHA256 a5acb092381dee5d99abd14b5310fc48695c81603b87c9ec20073e9034fe2d26
MD5 3bfbf60a6126ee04ad5cbec6a26f7ddc
BLAKE2b-256 b392e077c7d2184b2404fd397b3d2fb3803ad9a86532ec47a12bfd552044ecd9

See more details on using hashes here.

Provenance

The following attestation bundles were made for vcm_file_uploader-0.1.0.tar.gz:

Publisher: publish.yml on ATTMOS/vcm-file-uploader

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vcm_file_uploader-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for vcm_file_uploader-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1d3ac637144b31e816124a483f56caca7012b293b463796f70e4f565ecd10795
MD5 2885354797bbb8e362ff06b89ab11f4b
BLAKE2b-256 d100817d57a64bf336f023ab8636effeb44fe4bce62a048d500c4cad0bc00927

See more details on using hashes here.

Provenance

The following attestation bundles were made for vcm_file_uploader-0.1.0-py3-none-any.whl:

Publisher: publish.yml on ATTMOS/vcm-file-uploader

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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