mlflow/mlflow · error · Exception

Not an S3 URI: {uri}

Error message

Not an S3 URI: {uri}

What it means

parse_s3_compliant_uri on OptimizedS3ArtifactRepository only accepts URIs whose scheme is exactly 's3'; anything else raises a plain Exception. It is invoked from the constructor, so a bad URI fails at repo creation time.

Source

Thrown at mlflow/store/artifact/optimized_s3_artifact_repo.py:156

                "(e.g., os.environ['AWS_DEFAULT_REGION'] = 'us-gov-west-1')",
                error_code=INVALID_PARAMETER_VALUE,
            ) from error

    def _get_s3_client(self):
        return _get_s3_client(
            addressing_style=self._addressing_style,
            access_key_id=self._access_key_id,
            secret_access_key=self._secret_access_key,
            session_token=self._session_token,
            region_name=self._region_name,
            s3_endpoint_url=self._s3_endpoint_url,
        )

    def parse_s3_compliant_uri(self, uri):
        """Parse an S3 URI, returning (bucket, path)"""
        parsed = urllib.parse.urlparse(uri)
        if parsed.scheme != "s3":
            raise Exception(f"Not an S3 URI: {uri}")
        path = parsed.path
        path = path.removeprefix("/")
        return parsed.netloc, path

    @staticmethod
    def get_s3_file_upload_extra_args():
        if s3_file_upload_extra_args := MLFLOW_S3_UPLOAD_EXTRA_ARGS.get():
            return json.loads(s3_file_upload_extra_args)
        else:
            return None

    def _upload_file(self, s3_client, local_file, bucket, key):
        extra_args = {}
        extra_args.update(self._s3_upload_extra_args)
        guessed_type, guessed_encoding = guess_type(local_file)
        if guessed_type is not None:
            extra_args["ContentType"] = guessed_type
        if guessed_encoding is not None:

View on GitHub (pinned to 6a27f2decc)

Solutions

  1. Convert the URI to s3:// form: replace the scheme, keeping bucket and key (s3a://bucket/path -> s3://bucket/path).
  2. Ensure you are constructing the correct repo class for the scheme (e.g., LocalArtifactRepository for file:, R2ArtifactRepository for r2:).
  3. Validate the URI scheme before constructing the repo.
  4. Fix string-building bugs that drop or mangle the scheme.

Example fix

// before
repo = OptimizedS3ArtifactRepository("s3a://my-bucket/model")
# after
uri = "s3a://my-bucket/model".replace("s3a://", "s3://", 1)
repo = OptimizedS3ArtifactRepository(uri)
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse
def ensure_s3_uri(uri: str) -> str:
    if urlparse(uri).scheme != "s3":
        raise ValueError(f"Expected s3:// URI, got: {uri}")
    return uri
ensure_s3_uri(uri)  # call before constructing the repo

Type guard

def is_s3_uri(uri: str) -> bool:
    from urllib.parse import urlparse
    return urlparse(uri).scheme == "s3"

Try / catch

try:
    repo = OptimizedS3ArtifactRepository(uri)
except Exception as e:
    if str(e).startswith("Not an S3 URI"):
        repo = get_artifact_repository(uri)  # route by scheme
    else:
        raise

Prevention

When it happens

Trigger: Passing a URI like 's3a://bucket/key', 'file:///path', or an https URL into OptimizedS3ArtifactRepository (directly or via code that assumes S3).

Common situations: Using s3a:// (Spark-style) or S3 endpoints with custom schemes; string concatenation bugs producing malformed URIs; routing non-S3 artifact URIs into the optimized S3 repo.

Related errors


AI-assisted analysis of mlflow/mlflow@6a27f2decc (2026-08-29). Data as JSON: /api/errors/396b264cdee4478c. Report an issue: GitHub.