windmill-labs/windmill · error · ValueError

Invalid s3 object {s3_object!r}: expected an s3://<storage>/

Error message

Invalid s3 object {s3_object!r}: expected an s3://<storage>/<key> URI (e.g. 's3:///{s3_object}' for key {s3_object!r} in the default storage) or S3Object(s3=<key>)

What it means

The catch-all ValueError of the S3 object coercion helper: the value is neither a well-formed s3:// string URI nor an S3Object instance — e.g. a local path, a plain dict, an int, None, or an S3Object-like value from an older SDK version.

Source

Thrown at python-client/wmill/wmill/client.py:2331

def parse_s3_object(s3_object: S3Object | str) -> S3Object:
    """Parse S3 object from a `s3://<storage>/<key>` URI string (`s3:///<key>`
    for the default storage) or S3Object format. Any other string raises
    rather than falling back to an auto-generated key: an auto key is
    requested by omitting the object, and a fallback would silently misplace
    the upload on any typo.
    """
    if isinstance(s3_object, str):
        match = re.match(r'^s3://([^/]*)/(.+)$', s3_object)
        if match:
            return S3Object(s3=match.group(2), storage=match.group(1) or None)
        if s3_object.startswith("s3://"):
            raise ValueError(
                f"Invalid s3 object URI {s3_object!r}: expected "
                "s3://<storage>/<key> with a non-empty key "
                "(s3:///<key> for the default storage)"
            )
        raise ValueError(
            f"Invalid s3 object {s3_object!r}: expected an s3://<storage>/<key> "
            f"URI (e.g. 's3:///{s3_object}' for key {s3_object!r} in the default "
            "storage) or S3Object(s3=<key>)"
        )
    else:
        return s3_object

    

def parse_variable_syntax(s: str) -> Optional[str]:
    """Parse variable syntax from string."""
    if s.startswith("var://"):
        return s[6:]
    return None


def append_to_result_stream(text: str) -> None:
    """Append a text to the result stream.

View on GitHub (pinned to e474e8803c)

Solutions

  1. Pass an S3Object instance: S3Object(s3='key', storage=None)
  2. Pass a valid URI string 's3://<storage>/<key>' or 's3:///<key>'
  3. Convert plain keys explicitly: S3Object(s3=my_key)
  4. Type-check inputs (and reject None/local paths) before calling

Example fix

// before
wm.read_s3_file("/tmp/data.csv")  # local path, not an S3 object
// after
wm.read_s3_file("s3:///data.csv")  # or S3Object(s3="data.csv")
Defensive patterns

Strategy: type-guard

Validate before calling

import re
if not isinstance(s3_object, (str, S3Object)):
    raise TypeError(f"Expected s3:// URI or S3Object, got {type(s3_object).__name__}")

Type guard

def is_s3_object_like(v: object) -> bool:
    import re
    if isinstance(v, S3Object):
        return True
    return isinstance(v, str) and bool(re.match(r'^s3://([^/]*)/(.+)$', v))

Try / catch

try:
    obj = coerce_s3_object(value)
except ValueError:
    obj = S3Object(s3=str(value))  # only if value is a plain key

Prevention

When it happens

Trigger: Passing '/tmp/data.csv', {'s3': 'key'}, None, or any non-S3Object/non-URI value into functions accepting S3Object | str.

Common situations: Mixing local-file APIs with S3 APIs; constructing an object incorrectly after a library upgrade changed the S3Object type; passing values that are paths rather than s3:// URIs.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/e7c71ec97e46cdf6. Report an issue: GitHub.