@@ -35,6 +35,7 @@ def __init__(
3535 ):
3636 super ().__init__ (region , cache_client , resources , replace_existing )
3737 self ._credentials = credentials
38+ self ._s3_client = None
3839
3940 def _get_auth_headers (self ) -> dict [str , str ]:
4041 """Get authentication headers for Cloudflare API requests."""
@@ -52,6 +53,46 @@ def _get_auth_headers(self) -> dict[str, str]:
5253 else :
5354 raise RuntimeError ("Invalid Cloudflare credentials configuration" )
5455
56+ def _get_s3_client (self ):
57+ """
58+ Get or initialize the S3-compatible client for R2 operations.
59+
60+ :return: boto3 S3 client or None if credentials not available
61+ """
62+ if self ._s3_client is not None :
63+ return self ._s3_client
64+
65+ # Check if we have S3-compatible credentials
66+ if not self ._credentials .r2_access_key_id or not self ._credentials .r2_secret_access_key :
67+ self .logging .warning (
68+ "R2 S3-compatible API credentials not configured. "
69+ "Set CLOUDFLARE_R2_ACCESS_KEY_ID and CLOUDFLARE_R2_SECRET_ACCESS_KEY environment variables."
70+ )
71+ return None
72+
73+ try :
74+ import boto3
75+ from botocore .config import Config
76+
77+ account_id = self ._credentials .account_id
78+
79+ self ._s3_client = boto3 .client (
80+ 's3' ,
81+ endpoint_url = f'https://{ account_id } .r2.cloudflarestorage.com' ,
82+ aws_access_key_id = self ._credentials .r2_access_key_id ,
83+ aws_secret_access_key = self ._credentials .r2_secret_access_key ,
84+ config = Config (signature_version = 's3v4' ),
85+ region_name = 'auto'
86+ )
87+
88+ return self ._s3_client
89+
90+ except ImportError :
91+ self .logging .warning (
92+ "boto3 not available. Install with: pip install boto3"
93+ )
94+ return None
95+
5596 def correct_name (self , name : str ) -> str :
5697 return name
5798
@@ -142,33 +183,12 @@ def upload(self, bucket_name: str, filepath: str, key: str):
142183 :param filepath: local source filepath
143184 :param key: R2 destination key/path
144185 """
186+ s3_client = self ._get_s3_client ()
187+ if s3_client is None :
188+ self .logging .warning (f"Cannot upload { filepath } to R2 - S3 client not available" )
189+ return
190+
145191 try :
146- import boto3
147- from botocore .config import Config
148-
149- account_id = self ._credentials .account_id
150-
151- # R2 uses S3-compatible API, but requires special configuration
152- # The endpoint is: https://<account_id>.r2.cloudflarestorage.com
153- # You need to create R2 API tokens in the Cloudflare dashboard
154-
155- # Check if we have S3-compatible credentials
156- if not self ._credentials .r2_access_key_id or not self ._credentials .r2_secret_access_key :
157- self .logging .warning (
158- "R2 upload requires S3-compatible API credentials (r2_access_key_id, r2_secret_access_key). "
159- "File upload skipped. Set CLOUDFLARE_R2_ACCESS_KEY_ID and CLOUDFLARE_R2_SECRET_ACCESS_KEY."
160- )
161- return
162-
163- s3_client = boto3 .client (
164- 's3' ,
165- endpoint_url = f'https://{ account_id } .r2.cloudflarestorage.com' ,
166- aws_access_key_id = self ._credentials .r2_access_key_id ,
167- aws_secret_access_key = self ._credentials .r2_secret_access_key ,
168- config = Config (signature_version = 's3v4' ),
169- region_name = 'auto'
170- )
171-
172192 with open (filepath , 'rb' ) as f :
173193 s3_client .put_object (
174194 Bucket = bucket_name ,
@@ -178,11 +198,6 @@ def upload(self, bucket_name: str, filepath: str, key: str):
178198
179199 self .logging .debug (f"Uploaded { filepath } to R2 bucket { bucket_name } as { key } " )
180200
181- except ImportError :
182- self .logging .warning (
183- "boto3 not available. Install with: pip install boto3. "
184- "File upload to R2 skipped."
185- )
186201 except Exception as e :
187202 self .logging .warning (f"Failed to upload { filepath } to R2: { e } " )
188203
@@ -194,28 +209,12 @@ def upload_bytes(self, bucket_name: str, key: str, data: bytes):
194209 :param key: R2 destination key/path
195210 :param data: bytes to upload
196211 """
212+ s3_client = self ._get_s3_client ()
213+ if s3_client is None :
214+ self .logging .warning (f"Cannot upload bytes to R2 - S3 client not available" )
215+ return
216+
197217 try :
198- import boto3
199- from botocore .config import Config
200-
201- account_id = self ._credentials .account_id
202-
203- if not self ._credentials .r2_access_key_id or not self ._credentials .r2_secret_access_key :
204- self .logging .warning (
205- "R2 upload requires S3-compatible API credentials (r2_access_key_id, r2_secret_access_key). "
206- "Upload skipped. Set CLOUDFLARE_R2_ACCESS_KEY_ID and CLOUDFLARE_R2_SECRET_ACCESS_KEY environment variables."
207- )
208- return
209-
210- s3_client = boto3 .client (
211- 's3' ,
212- endpoint_url = f'https://{ account_id } .r2.cloudflarestorage.com' ,
213- aws_access_key_id = self ._credentials .r2_access_key_id ,
214- aws_secret_access_key = self ._credentials .r2_secret_access_key ,
215- config = Config (signature_version = 's3v4' ),
216- region_name = 'auto'
217- )
218-
219218 s3_client .put_object (
220219 Bucket = bucket_name ,
221220 Key = key ,
@@ -224,10 +223,6 @@ def upload_bytes(self, bucket_name: str, key: str, data: bytes):
224223
225224 self .logging .debug (f"Uploaded { len (data )} bytes to R2 bucket { bucket_name } as { key } " )
226225
227- except ImportError :
228- self .logging .warning (
229- "boto3 not available. Install with: pip install boto3"
230- )
231226 except Exception as e :
232227 self .logging .warning (f"Failed to upload bytes to R2: { e } " )
233228
@@ -246,27 +241,12 @@ def list_bucket(self, bucket_name: str, prefix: str = "") -> List[str]:
246241 :param prefix: optional prefix filter
247242 :return: list of files in a given bucket
248243 """
249- # Use S3-compatible API with R2 credentials
250- if not self . _credentials . r2_access_key_id or not self . _credentials . r2_secret_access_key :
251- self .logging .warning (f"R2 S3 credentials not configured, cannot list bucket { bucket_name } " )
244+ s3_client = self . _get_s3_client ()
245+ if s3_client is None :
246+ self .logging .warning (f"Cannot list R2 bucket { bucket_name } - S3 client not available " )
252247 return []
253248
254249 try :
255- import boto3
256- from botocore .config import Config
257-
258- account_id = self ._credentials .account_id
259- r2_endpoint = f"https://{ account_id } .r2.cloudflarestorage.com"
260-
261- s3_client = boto3 .client (
262- 's3' ,
263- endpoint_url = r2_endpoint ,
264- aws_access_key_id = self ._credentials .r2_access_key_id ,
265- aws_secret_access_key = self ._credentials .r2_secret_access_key ,
266- config = Config (signature_version = 's3v4' ),
267- region_name = 'auto'
268- )
269-
270250 # List objects with optional prefix
271251 paginator = s3_client .get_paginator ('list_objects_v2' )
272252 page_iterator = paginator .paginate (Bucket = bucket_name , Prefix = prefix )
0 commit comments