From ade12b5f70823ccbe6e9a3f6b5975f7e2a5680f7 Mon Sep 17 00:00:00 2001 From: pandyamarut Date: Tue, 1 Jul 2025 13:29:53 -0700 Subject: [PATCH 1/4] feat: add working example for S3 compatible API with tetra Signed-off-by: pandyamarut --- examples/s3_comp_api.py | 121 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 examples/s3_comp_api.py diff --git a/examples/s3_comp_api.py b/examples/s3_comp_api.py new file mode 100644 index 0000000..55ff9cc --- /dev/null +++ b/examples/s3_comp_api.py @@ -0,0 +1,121 @@ +# Example: Simple S3 Upload and Download with RunPod +# This script creates a simple image, uploads it to S3, and then downloads it back +# using the RunPod API. It demonstrates how to use the remote function decorator +# to run code on a remote GPU instance, and how to handle S3 Compatible API operations on Runpod with boto3. + + + +import asyncio +import os +from tetra_rp import remote, LiveServerless, GpuGroup, PodTemplate + +# Simple GPU config +gpu_config = LiveServerless( + name="simple-s3-testssfsfkjkjs", + gpus=[GpuGroup.AMPERE_24], + template=PodTemplate( + containerDiskInGb=30, + env=[ + {"key": "AWS_ACCESS_KEY_ID", "value": os.getenv("AWS_ACCESS_KEY_ID", "")}, + {"key": "AWS_SECRET_ACCESS_KEY", "value": os.getenv("AWS_SECRET_ACCESS_KEY", "")} + ] + ), + workersMax=1 +) + +@remote( + resource_config=gpu_config, + dependencies=["boto3", "pillow"] +) +def create_and_upload_image(): + """Create a simple colored image and upload to S3""" + import boto3 + import os + import io + from PIL import Image + + print("Creating simple image...") + + # Create a simple 512x512 blue image + image = Image.new('RGB', (512, 512), color='blue') + + # Convert to bytes + img_buffer = io.BytesIO() + image.save(img_buffer, format='PNG') + img_bytes = img_buffer.getvalue() + + print("Uploading to S3...") + + # Upload to S3 + s3_client = boto3.client( + 's3', + endpoint_url="https://s3api-eur-is-1.runpod.io/", + region_name="EUR-IS-1", + aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID"), + aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY") + ) + + s3_client.put_object( + Bucket="", # Change this to your volume ID + Key="test_image.png", + Body=img_bytes, + ContentType='image/png' + ) + + print("Upload complete!") + return "s3:///test_image.png" + +def download_image_from_s3(s3_path: str, local_filename: str): + """Download image from S3 to local file""" + import boto3 + import os + + print(f"Downloading {s3_path} to {local_filename}...") + + # Extract bucket and key from s3 path + # s3://bucket/key -> bucket, key + parts = s3_path.replace("s3://", "").split("/", 1) + bucket = parts[0] + key = parts[1] + + # Create S3 client + s3_client = boto3.client( + 's3', + endpoint_url="https://s3api-eur-is-1.runpod.io/", + region_name="EUR-IS-1", + aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID"), + aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY") + ) + + try: + # Download the file + s3_client.download_file(bucket, key, local_filename) + print(f"Downloaded to {local_filename}") + return True + except Exception as e: + print(f"Download failed: {e}") + return False + +# Add this to your main function: +async def main(): + print("Simple S3 Test") + + # Check credentials + if not os.getenv("AWS_ACCESS_KEY_ID"): + print("Set AWS_ACCESS_KEY_ID environment variable") + return + + if not os.getenv("AWS_SECRET_ACCESS_KEY"): + print("Set AWS_SECRET_ACCESS_KEY environment variable") + return + + # Run the remote function + result = await create_and_upload_image() + print(f"Result: {result}") + + # Download it locally + download_image_from_s3(result, "downloaded_test_image.png") + print("Complete workflow: Generate → Upload → Download!") + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file From 94448e5a3e3c55150702057e5a7789d0fa627056 Mon Sep 17 00:00:00 2001 From: pandyamarut Date: Tue, 1 Jul 2025 13:31:14 -0700 Subject: [PATCH 2/4] format Signed-off-by: pandyamarut --- examples/s3_comp_api.py | 65 +++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/examples/s3_comp_api.py b/examples/s3_comp_api.py index 55ff9cc..492ac9d 100644 --- a/examples/s3_comp_api.py +++ b/examples/s3_comp_api.py @@ -4,7 +4,6 @@ # to run code on a remote GPU instance, and how to handle S3 Compatible API operations on Runpod with boto3. - import asyncio import os from tetra_rp import remote, LiveServerless, GpuGroup, PodTemplate @@ -17,76 +16,78 @@ containerDiskInGb=30, env=[ {"key": "AWS_ACCESS_KEY_ID", "value": os.getenv("AWS_ACCESS_KEY_ID", "")}, - {"key": "AWS_SECRET_ACCESS_KEY", "value": os.getenv("AWS_SECRET_ACCESS_KEY", "")} - ] + { + "key": "AWS_SECRET_ACCESS_KEY", + "value": os.getenv("AWS_SECRET_ACCESS_KEY", ""), + }, + ], ), - workersMax=1 + workersMax=1, ) -@remote( - resource_config=gpu_config, - dependencies=["boto3", "pillow"] -) + +@remote(resource_config=gpu_config, dependencies=["boto3", "pillow"]) def create_and_upload_image(): """Create a simple colored image and upload to S3""" import boto3 import os import io from PIL import Image - + print("Creating simple image...") - + # Create a simple 512x512 blue image - image = Image.new('RGB', (512, 512), color='blue') - + image = Image.new("RGB", (512, 512), color="blue") + # Convert to bytes img_buffer = io.BytesIO() - image.save(img_buffer, format='PNG') + image.save(img_buffer, format="PNG") img_bytes = img_buffer.getvalue() - + print("Uploading to S3...") - + # Upload to S3 s3_client = boto3.client( - 's3', + "s3", endpoint_url="https://s3api-eur-is-1.runpod.io/", region_name="EUR-IS-1", aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID"), - aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY") + aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY"), ) - + s3_client.put_object( Bucket="", # Change this to your volume ID Key="test_image.png", Body=img_bytes, - ContentType='image/png' + ContentType="image/png", ) - + print("Upload complete!") return "s3:///test_image.png" + def download_image_from_s3(s3_path: str, local_filename: str): """Download image from S3 to local file""" import boto3 import os - + print(f"Downloading {s3_path} to {local_filename}...") - + # Extract bucket and key from s3 path # s3://bucket/key -> bucket, key parts = s3_path.replace("s3://", "").split("/", 1) bucket = parts[0] key = parts[1] - + # Create S3 client s3_client = boto3.client( - 's3', + "s3", endpoint_url="https://s3api-eur-is-1.runpod.io/", region_name="EUR-IS-1", aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID"), - aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY") + aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY"), ) - + try: # Download the file s3_client.download_file(bucket, key, local_filename) @@ -96,26 +97,28 @@ def download_image_from_s3(s3_path: str, local_filename: str): print(f"Download failed: {e}") return False + # Add this to your main function: async def main(): print("Simple S3 Test") - + # Check credentials if not os.getenv("AWS_ACCESS_KEY_ID"): print("Set AWS_ACCESS_KEY_ID environment variable") return - + if not os.getenv("AWS_SECRET_ACCESS_KEY"): print("Set AWS_SECRET_ACCESS_KEY environment variable") return - + # Run the remote function result = await create_and_upload_image() print(f"Result: {result}") - + # Download it locally download_image_from_s3(result, "downloaded_test_image.png") print("Complete workflow: Generate → Upload → Download!") + if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file + asyncio.run(main()) From 1245cca6b665f5b36b696908a57e8bf32447ae39 Mon Sep 17 00:00:00 2001 From: pandyamarut Date: Tue, 1 Jul 2025 13:38:56 -0700 Subject: [PATCH 3/4] update a comment Signed-off-by: pandyamarut --- examples/s3_comp_api.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/examples/s3_comp_api.py b/examples/s3_comp_api.py index 492ac9d..8b6e255 100644 --- a/examples/s3_comp_api.py +++ b/examples/s3_comp_api.py @@ -3,6 +3,13 @@ # using the RunPod API. It demonstrates how to use the remote function decorator # to run code on a remote GPU instance, and how to handle S3 Compatible API operations on Runpod with boto3. +# Set up your AWS credentials as environment variables: +# export AWS_ACCESS_KEY_ID=your_access_key_id +# export AWS_SECRET_ACCESS_KEY=your_secret_access_key +# replace RUNPOD_S3_ENDPOINT with your Runpod S3 endpoint if different, +# e.g., https://s3api-eur-is-1.runpod.io/ +# replace with your actual Runpod volume ID, e.g., p23s969vxz + import asyncio import os @@ -56,14 +63,14 @@ def create_and_upload_image(): ) s3_client.put_object( - Bucket="", # Change this to your volume ID + Bucket="p23s969vxz", # Change this to your volume ID Key="test_image.png", Body=img_bytes, ContentType="image/png", ) print("Upload complete!") - return "s3:///test_image.png" + return "s3://p23s969vxz/test_image.png" def download_image_from_s3(s3_path: str, local_filename: str): From 8d5ed883ecce115376ea81e4e4c2d9f644103a02 Mon Sep 17 00:00:00 2001 From: pandyamarut Date: Tue, 1 Jul 2025 21:21:33 -0700 Subject: [PATCH 4/4] update example Signed-off-by: pandyamarut --- examples/s3_comp_api.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/s3_comp_api.py b/examples/s3_comp_api.py index 8b6e255..fa314bd 100644 --- a/examples/s3_comp_api.py +++ b/examples/s3_comp_api.py @@ -17,7 +17,7 @@ # Simple GPU config gpu_config = LiveServerless( - name="simple-s3-testssfsfkjkjs", + name="simple-s3-example", gpus=[GpuGroup.AMPERE_24], template=PodTemplate( containerDiskInGb=30, @@ -64,13 +64,13 @@ def create_and_upload_image(): s3_client.put_object( Bucket="p23s969vxz", # Change this to your volume ID - Key="test_image.png", + Key="example_image.png", Body=img_bytes, ContentType="image/png", ) print("Upload complete!") - return "s3://p23s969vxz/test_image.png" + return "s3://p23s969vxz/example_image.png" def download_image_from_s3(s3_path: str, local_filename: str): @@ -107,7 +107,7 @@ def download_image_from_s3(s3_path: str, local_filename: str): # Add this to your main function: async def main(): - print("Simple S3 Test") + print("Simple S3 example") # Check credentials if not os.getenv("AWS_ACCESS_KEY_ID"): @@ -123,7 +123,7 @@ async def main(): print(f"Result: {result}") # Download it locally - download_image_from_s3(result, "downloaded_test_image.png") + download_image_from_s3(result, "downloaded_example_image.png") print("Complete workflow: Generate → Upload → Download!")