11"""S3-compatible object storage configuration."""
22
3+ from typing import Any , Optional
4+
5+ import boto3
36from pydantic import Field
47from pydantic_settings import BaseSettings
58
69
710class S3Config (BaseSettings ):
8- """S3-compatible storage settings (Garage, AWS S3, etc.)."""
11+ """S3-compatible storage settings (Garage, AWS S3, etc.).
12+
13+ When ``access_key`` and ``secret_key`` are ``None`` (the default), boto3
14+ uses its standard credential chain — environment variables,
15+ ``~/.aws/credentials``, and EC2/ECS instance metadata (IAM role). Set
16+ them explicitly only when connecting to a non-AWS S3-compatible service
17+ such as Garage or MinIO that requires static credentials.
18+ """
919
1020 endpoint : str = Field (default = "localhost:3900" , alias = "s3_endpoint" )
11- access_key : str = Field (
12- default = "test-access-key" , min_length = 3 , alias = "s3_access_key"
13- )
14- secret_key : str = Field (
15- default = "test-secret-key" , min_length = 8 , alias = "s3_secret_key"
16- )
21+ access_key : Optional [str ] = Field (default = None , alias = "s3_access_key" )
22+ secret_key : Optional [str ] = Field (default = None , alias = "s3_secret_key" )
1723 secure : bool = Field (default = False , alias = "s3_secure" )
1824 bucket : str = Field (default = "code-interpreter-files" , alias = "s3_bucket" )
1925 region : str = Field (default = "garage" , alias = "s3_region" )
@@ -24,6 +30,31 @@ def endpoint_url(self) -> str:
2430 scheme = "https" if self .secure else "http"
2531 return f"{ scheme } ://{ self .endpoint } "
2632
33+ def make_client (self ) -> Any :
34+ """Return a configured boto3 S3 client.
35+
36+ Credentials are passed explicitly only when both ``access_key`` and
37+ ``secret_key`` are set. When they are ``None``, boto3 falls through to
38+ its default credential chain (env vars, ``~/.aws/credentials``, EC2/ECS
39+ instance metadata).
40+
41+ Raises ``ValueError`` when exactly one of the pair is set — partial
42+ static config is always a misconfiguration.
43+ """
44+ if bool (self .access_key ) != bool (self .secret_key ):
45+ raise ValueError (
46+ "S3_ACCESS_KEY and S3_SECRET_KEY must both be set or both be unset. "
47+ "Partial static credentials are not supported."
48+ )
49+ kwargs : dict [str , Any ] = {
50+ "endpoint_url" : self .endpoint_url ,
51+ "region_name" : self .region ,
52+ }
53+ if self .access_key and self .secret_key :
54+ kwargs ["aws_access_key_id" ] = self .access_key
55+ kwargs ["aws_secret_access_key" ] = self .secret_key
56+ return boto3 .client ("s3" , ** kwargs )
57+
2758 class Config :
2859 env_prefix = ""
2960 extra = "ignore"
0 commit comments