cocoindex-io/cocoindex · error · ValueError

Invalid S3 URI {uri!r}: expected 's3://bucket/key'.

Error message

Invalid S3 URI {uri!r}: expected 's3://bucket/key'.

What it means

_parse_s3_uri validates that an S3 URI starts with the `s3://` scheme before splitting it into bucket and key. When the string does not begin with `s3://`, it refuses to parse and raises this ValueError instead of producing a wrong bucket/key pair.

Source

Thrown at python/cocoindex/connectors/amazon_s3/_source.py:50

from cocoindex.resources import file


def _etag_to_fingerprint(etag: object) -> bytes | None:
    """Convert an S3 ETag (a ``str``) to the ``bytes`` content fingerprint.

    botocore returns ETags as quoted strings (e.g. ``'"d41d8cd9..."'``), but
    :class:`~cocoindex.resources.file.FileMetadata.content_fingerprint` is typed
    ``bytes``.  Storing the raw ``str`` makes the memo state's
    ``tuple[datetime, bytes]`` round-trip fail on re-run (msgspec encodes a str
    but the decoder expects bin), so encode it to bytes here.
    """
    return etag.encode("utf-8") if isinstance(etag, str) else None


def _parse_s3_uri(uri: str) -> tuple[str, str]:
    """Parse an ``s3://bucket/key`` URI into *(bucket_name, key)*."""
    if not uri.startswith("s3://"):
        raise ValueError(f"Invalid S3 URI {uri!r}: expected 's3://bucket/key'.")
    without_scheme = uri[len("s3://") :]
    slash_idx = without_scheme.find("/")
    if slash_idx == -1:
        raise ValueError(f"Invalid S3 URI {uri!r}: expected 's3://bucket/key'.")
    return without_scheme[:slash_idx], without_scheme[slash_idx + 1 :]


class S3FilePath(file.FilePath[str]):
    """
    File path for Amazon S3 objects.

    The resolved path is the full S3 object key (string).
    The relative path is the object key relative to the walker prefix (or the full key
    if no prefix was used).
    """

    __slots__ = ("_bucket_name", "_object_key")

View on GitHub (pinned to e84aa99b32)

Solutions

  1. Prefix the argument with `s3://`: use `s3://my-bucket/data/config.json`.
  2. If you only have bucket and key separately, call get_object(client, bucket, key) with two arguments instead.
  3. Normalize programmatic input: f"s3://{bucket}/{key}" before calling the API.
  4. Strip non-s3 schemes first if you receive https URLs and convert them to s3:// form.

Example fix

// before
f = await amazon_s3.get_object(client, "my-bucket/data/config.json")

// after
f = await amazon_s3.get_object(client, "s3://my-bucket/data/config.json")
Defensive patterns

Strategy: validation

Validate before calling

def ensure_s3_uri(uri: str) -> str:
    if not uri.startswith("s3://"):
        raise ValueError(f"not an s3 URI: {uri!r}")
    if "/" not in uri[5:]:
        raise ValueError(f"s3 URI missing key: {uri!r}")
    return uri

Type guard

def is_s3_uri(value: object) -> bool:
    return isinstance(value, str) and value.startswith("s3://") and "/" in value[5:]

Try / catch

try:
    f = await amazon_s3.get_object(client, uri)
except ValueError as e:
    logger.error("bad S3 URI: %s", e)
    raise

Prevention

When it happens

Trigger: Calling amazon_s3.get_object(client, 'my-bucket/data/config.json') or amazon_s3.read(client, 'my-bucket/key') with a URI missing the `s3://` prefix.

Common situations: Passing a bare bucket/key string, an https:// console URL, or a path copied from a local filesystem into functions documented to take an s3:// URI.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08). Data as JSON: /api/errors/6745dc296dcc1537. Report an issue: GitHub.