Minimizing Installer Size: A Python Script for Efficient S3 Download and Execution

Umesh Sujakhu
codingmountain
Published in
5 min readJan 19, 2024
Photo by Luca Bravo on Unsplash

Introduction

In the world of software distribution, minimizing installer size is often crucial for user experience and resource efficiency. This post explores a Python script that downloads an executable file (.exe) from an S3 bucket and executes it, all while keeping the script size minimal. This approach becomes particularly useful when dealing with large-sized installers. Let’s dive into the details step by step.

Prerequisites

Before we begin, make sure you have the following installed:

  • Python 3.11
  • Pip
  • Pyinstaller
  • Boto3 (AWS SDK for Python)
  • tqdm (A fast, extensible progress bar for Python)
pip install boto3 tqdm

First, we need to create two Python files, one main.py and s3_service.py

We need s3_service.py file to initialize the AWS S3 bucket and download the file from the bucket provided.

s3_service.py

import boto3
import os


class S3Service:
def __init__(self):
self.aws_access_key = "YOUR_AWS_ACCESS_KEY"
self.aws_secret_key = "YOUR_AWS_SECRET_KEY"

self.s3 = boto3.client(
"s3",
aws_access_key_id=self.aws_access_key,
aws_secret_access_key=self.aws_secret_key,
)

def set_s3_parameters(self, bucket_name, object_key, local_path):
self.bucket_name = bucket_name
self.object_key = object_key
self.local_path = os.path.join(local_path, object_key)

We have imported the modules for :

  • boto3: The AWS SDK for Python, used for interacting with AWS services.
  • os: Provides a way of interacting with the operating system.

S3Service __init__ method initializes the AWS access key, and secret key and creates an S3 client. Whereas the set_s3_parametersfunction sets the S3 parameters(bucket name, object key, local path)

Make sure to replace the placeholder `YOUR_AWS_ACCESS_KEY` and `YOUR_AWS_SECRET_KEY` with your actual AWS credentials.

Now, we will add the function to download the executable file from the S3 bucket.

from tqdm import tqdm

def download_file_with_progress(self):
# Get the size of the S3 object for tqdm progress bar
response = self.s3.head_object(Bucket=self.bucket_name, Key=self.object_key)
file_size = response["ContentLength"]

# Use tqdm to display a progress bar
with tqdm(
total=file_size,
desc="Downloading...Please wait...",
unit="B",
unit_scale=True,
) as pbar:
self.s3.download_file(
self.bucket_name, self.object_key, self.local_path, Callback=pbar.update
)

def execute_downloaded_file(self):
# Execute the downloaded executable
os.system(self.local_path)

The download_file_with_progress the method retrieves the size of the S3 object for progress bar initialization. We have used atqdm module to display a progress bar during the download. This function will download the file with a callback to update the progress bar.

The execute_downloaded_file function is to execute the file after the download completes.

Now, let us write a code for the main.py file.

main.py

import sys
from s3_service import S3Service


def get_destination_path():
return 'C:\\Users\\umesh\\Downloads'


def main(s3_service):
try:
s3_service.set_s3_parameters(
bucket_name="YOUR_BUCKET_NAME",
object_key="YOUR_EXE_FILE_NAME_IN_BUCKET",
local_path=get_destination_path(),
)
s3_service.download_file_with_progress()
s3_service.execute_downloaded_file()
sys.exit()
except KeyboardInterrupt:
print("\nScript interrupted. Performing cleanup or exiting gracefully.")
finally:
print("Exiting the script.")


if __name__ == "__main__":
s3_service = S3Service()
main(s3_service)

Importing Modules:

  • sys: Provides access to some variables used or maintained by the Python interpreter.
  • S3Service: The custom class defined s3_service.py for interacting with AWS S3.

get_destination_path is to return the destination path of your device where you want to save the downloaded file.

main function configures the S3 parameters, and calls the S3 service to download the file with progress, and executes the downloaded file after the download is completed.

You can now run the script by command:

python main.py

You will now see the output below:

If you want to show a banner to make it look more user-friendly, you can show a banner as well.

Let’s add a function in s3_service.py to show a banner and call the function from download_file_with_progress function

 def show_banner(self):
banner = """
____ _ _ _
| _ \ _____ ___ __ | | ___ __ _ __| (_)_ __ __ _
| | | |/ _ \ \ /\ / / '_ \| |/ _ \ / _` |/ _` | | '_ \ / _` |
| |_| | (_) \ V V /| | | | | (_) | (_| | (_| | | | | | (_| |
|____/ \___/ \_/\_/ |_| |_|_|\___/ \__,_|\__,_|_|_| |_|\__, |
|___/
"""
print(banner)

So the final s3_service.py will look like

import boto3
import os
from tqdm import tqdm


class S3Service:
def __init__(self):
self.aws_access_key = "YOUR_AWS_ACCESS_KEY"
self.aws_secret_key = "YOUR_AWS_SECRET_KEY"

self.s3 = boto3.client(
"s3",
aws_access_key_id=self.aws_access_key,
aws_secret_access_key=self.aws_secret_key,
)

def set_s3_parameters(self, bucket_name, object_key, local_path):
self.bucket_name = bucket_name
self.object_key = object_key
self.local_path = os.path.join(local_path, object_key)

def show_banner(self):
banner = """
____ _ _ _
| _ \ _____ ___ __ | | ___ __ _ __| (_)_ __ __ _
| | | |/ _ \ \ /\ / / '_ \| |/ _ \ / _` |/ _` | | '_ \ / _` |
| |_| | (_) \ V V /| | | | | (_) | (_| | (_| | | | | | (_| |
|____/ \___/ \_/\_/ |_| |_|_|\___/ \__,_|\__,_|_|_| |_|\__, |
|___/
"""
print(banner)

def download_file_with_progress(self):
# Get the size of the S3 object for tqdm progress bar
response = self.s3.head_object(Bucket=self.bucket_name, Key=self.object_key)
file_size = response["ContentLength"]

self.show_banner()

# Use tqdm to display a progress bar
with tqdm(
total=file_size,
desc="Downloading...Please wait...",
unit="B",
unit_scale=True,
) as pbar:
self.s3.download_file(
self.bucket_name, self.object_key, self.local_path, Callback=pbar.update
)

def execute_downloaded_file(self):
# Execute the downloaded executable
os.system(self.local_path)

Now let us see how the output looks when we run the script :

Finally, to generate a standalone executable file for the script we have written, we will use pyinstaller command :

pyinstaller --onefile main.py

This command will generate an .exe file in the dist folder in your project root directory.

Conclusion

This Python script provides a simple yet effective solution for providing minimum-size installer files to users, by a smart way of downloading and executing executable files directly from an AWS S3 bucket. Feel free to customize and integrate it into your deployment workflows to enhance user experiences with more efficient software installations.

In this topic, I have covered creating a script to install executable files from S3, but it shows the download progress in the command line terminal. I will cover how we can hide the terminal and show the download progress in a user-friendly GUI in the next topic.

--

--