Tag: Python

The tag for Programming Languages posts about Python programming.

  • I Refactored Easy Home Directory Backup

    I Refactored Easy Home Directory Backup

    Some time back I wrote an introduction to my newest application available on GitHub at the time: Easy Home Directory Backup. Excitement gripped me as I celebrated the release here on my website and on my social media accounts. Later, when I wanted to add a new feature to the program I discovered something terrible: Some parts of the application didn’t work at all. That meant I couldn’t add the new feature until I refactored Easy Home Directory Backup.

    Why I Refactored Easy Home Directory Backup

    In the first version of the application I gave users the options to perform two types of partial backups:

    • All files except dot (.) files
    • Only dot (.) files

    Well, the first option worked, but not the second. Looking back I didn’t test the second option enough to realize my code was incorrect. Where I messed up was the option I passed into the rsync command:

    subprocess.run(["rsync", "-az", "--exclude=* --include=.*",
                                        f"--log-file={path}/easy_home_directory_backup_{formatted_today_date}_log_file",
                                        home_dir_path, backup_device_path])

    I incorrectly thought the –exclude=* –include=.* code would exclude all the files in the user’s Home Directory, and then back up only the dot (.) files. That didn’t happen when I ran the program on my computer. Thus, I worked to fix my mistake. However, I couldn’t.

    I tried all different combinations, but I couldn’t get this partial type of backup to work. Every time the application would back up both the non-dot (.) and dot (.) files.

    At an impasse I actually thought about this part of my program. I asked myself these questions:

    • Is this type of partial backup actually useful?
    • Would the end user choose this option?
    • Does anyone really want to back up all their dot (.) files?

    After determining this use case was too small I knew it was time for a change. Thus, I refactored Easy Home Directory.

    What Did I Change?

    In total I changed ten files. The file seeing the biggest change was src/partial_backup.py as I removed 81 lines.

    The determine_partial_backup_type function disappeared completely because there wasn’t two different partial backup options anymore. That accounted for 76 lines of code I deleted. This made the partial_backup function shorter in length, and easier to read and understand. The main code runs in these 26 lines:

            else:
                # If the user-supplied backup device path is invalid
                if not validate_backup_path(device_path):
                    partial_backup()
                else:
                    check_free_space_for_backup(path, home_dir_path)
                    today_date: datetime = datetime.today()
                    formatted_today_date_and_time: str = today_date.strftime("%b-%d-%Y_%I:%M%p")
                    # Create a directory on the backup device named after the formatted today's date of the backup
                    backup_device_path: str = f"{path}/{formatted_today_date_and_time}"
                    make_log_file_directory()
                    console.print()
                    console.print("Preparing to start the partial backup in a minute or so.", justify="center")
                    console.print()
                    # Progress bar from tqdm
                    for _ in tqdm(range(100), desc="Backup Progress", unit="GB"):
                        """rsync options
                        a = Archive
                        """
                        subprocess.run(["rsync", "-a", "--exclude=.*",
                                        f"--log-file={home_dir_path}/.easy_home_directory_backup/{formatted_today_date_and_time}_log_file",
                                        home_dir_path, backup_device_path])
                    console.print()
                    console.print(f"A log of this backup can be found in {path}/.easy_home_directory_backup",
                                  justify="center")
                    console.print()
                    sys.exit()

    I made other changes to the additional nine files in the project. Much of it was changing the text in the comments to make the language more uniform. Or I added missing type annotations. Oh, and I added a new file (src/log_file_directory.py) to create the hidden directory containing the application’s log file.

    To review these changes and more, please check out the project’s GitHub repo. That’s where one can download the application too.

  • Where Should I Go Eat: An Introduction

    Where Should I Go Eat: An Introduction

    The last of my technical projects I haven’t talked about yet is Where Should I Go Eat (which you can visit here). In this post I will discuss the following topics about this web application:

    • Why I created it
    • The code running it
    • My future plans for the web application

    Why I Created Where Should I Go Eat

    This story is similar to my story for creating Black History Fact Generator: The year was 2019. I worked at Twitter as a Site Operations Technician at the Atlanta, GA data center. The year prior I taught myself the Python programming language using an Udemy course, and I found myself wanting to create more projects to increase that skill.

    Since I worked at the data center and not an actual Twitter office, the company provided a catered Monday through Thursday. Every Friday we had the opportunity to spend our daily lunch budget ($19 at the time) on our own. We could order lunch via Doordash or go out to eat. Usually, we chose the former because lunchtime traffic in Downtown Atlanta sucks.

    My team always fought about which restaurant to order from. Some people wanted fast food like Chick-fil-A. Others wanted to eat from the newest hip restaurant. We would argue back and forth in the Slack channel dedicated to our lunch order.

    One day I made a joke that I should build an app that chose a restaurant at random for us. That got a few laughs, and one of the Site Reliability Engineers (SREs) on my team told me to do just that. He knew I taught myself Python and that project would get me to flex those skills. So that’s what I did.

    I brainstormed names for my app, decided on Where Should I Go Eat, and bought a domain name from Namecheap.

    The Code Running Where Should I Go Eat

    All the code can be found on the project’s GitHub repo.

    Front-End Code

    I used Bootstrap to build out the Front-End of the website because of the following reasons:

    • Ease to use
    • Rich responsive website design features
    • Offers good performance

    In addition to Bootstrap I use regular (or vanilla) HTML and CSS to build out the Front-End.

    Back-End Code

    I used Python and Flask to build out the Back-end of the website. I chose to use Flask over Django because it requires much less code to spin up a simple application.

    The Python code to randomly choose a restaurant is in the main.py file. I’ll include a snippet of that file below:

    with open("static/files/restaurants.txt", "r") as restaurants:
            restaurant_list: [str] = restaurants.readlines()
    
        # Choose a random restaurant
        random_restaurant: [str] = random.choice(restaurant_list)
    
        # Strip the newline character from the end so the search is successful
        random_restaurant: [str] = random_restaurant.strip()
    
        # Initializing variable
        found_restaurant_url: str = ""
    
        # Search the nested tuples for the correct restaurant URLs matching the chosen random restaurant
        for restaurant in restaurant_urls:
            if random_restaurant in restaurant:
                found_restaurant_url: str = restaurant[1]

    Let’s go through the code line by line:

    1. I have all the restaurants in a text file called restaurants.txt. Using the “with open” command I read all the names into a list called “restaurant_list.”
    2. Then I use the “random.choice” function to choose a random restaurant from the “restaurant_list” list.
    3. After that I strip the newline character from the end of the “random_restaurant” so when I search for it, the exact match appears.
    4. I initialize a variable to hold the “random_restaurant” URL.
    5. Finally, I use a “for” loop to search through the tuple containing the restaurants and their URLs in the “restaurants_urls.py” file, and assign the one matching the “random_restaurant.”

    The main.py file contains all the route (or links) to the home page, which displays the random restaurant and its URL.

    I include the restaurant on the home page template as so:

    <div class="col">
                <button class="button" name="start-over" type="submit" onclick="location.href=location.href">Reset</button>
            </div>
            <p class="restaurant-result">You Should
                Go Eat At {{ restaurant }}</p>
            <p class="restaurant-link">Order Online At {{ restaurant }} <a href="{{ restaurant_link }}"
                                                                           target="_blank">here</a></p>
        </div>
    </div>

    If users wants to generate another random restaurant they click the button on screen, which runs the function again.

    My Future Plans For This Application

    I had some plans to use geolocation to provide restaurants available in that visitor’s location, but I haven’t worked on that feature. I may in the future.

  • Click-Go-Lotto: An Introduction

    Click-Go-Lotto: An Introduction

    One of my technical projects I updated recently is Click-Go-Lotto (which you can visit here). In this post I will discuss the following topics about this web application:

    • Why I created it
    • The code running it
    • My future plans for the web application

    Why I Created Click-Go-Lotto

    My mom plays the Georgia lottery, both scratch-off tickets and lottery games like Mega Millions, Powerball, and Fantasy Five to name a few.

    Like many lottery players she has her a set of favorite numbers to play for particular games over and over. However, there are times she allows the machine to randomly choose her numbers.

    Instead of allowing the lottery machine to choose numbers for her, I decided to create a web app that could do the same. A benefit of a web app over a lottery machine is that she can generate as many random numbers as she wants without paying for them. My mom could use the app on her phone if she’s on the go, or on her laptop when she’s at home.

    After brainstorming names for my app, I picked Click-Go-Lotto because users would click a button on the website to get the numbers. After coming up with the name I bought the domain name from Namecheap.

    With the domain name in hand, it was time to design the web application. I already knew what tech stack I wanted to use: Bootstrap for the Front-End and Python/Flask for the Back-End.

    The Code Running Click-Go-Lotto

    All the code can be found on the project’s GitHub repo.

    Front-End Code

    I used Bootstrap to build out the Front-End of the website because of the following reasons:

    • Ease to use
    • Rich responsive website design features
    • Offers good performance

    In addition to Bootstrap I use regular (or vanilla) HTML and CSS to build out the Front-End.

    Back-End Code

    I used Python and Flask to build out the Back-end of the website. I chose to use Flask over Django because it requires much less code to spin up a simple application.

    The Python code to generate the random numbers is all in one module: games.py. I’ll include a snippet of that file below:

    def cash3() -> tuple[int, int, int]:
        """This function provides the three numbers for the Cash 3 game.
        The game allows for numbers 0 to 9, and also allows for duplicate numbers.
        :return: Three individual random integers.
        """
        num1: int = random.randint(0, 9)
        num2: int = random.randint(0, 9)
        num3: int = random.randint(0, 9)
        return num1, num2, num3
    
    ...
    
    def mega_millions() -> tuple[int, int, int, int, int, int]:
        """This function provides the six numbers for the Mega Millions game.
        The game allows for numbers 1 to 60, but doesn't allow for duplicate numbers except for the Mega Ball.
        :return: Six individual random integers.
        """
        num1: int = random.randint(1, 13)
        num2: int = random.randint(14, 26)
        num3: int = random.randint(27, 39)
        num4: int = random.randint(40, 52)
        num5: int = random.randint(53, 70)
        mega_ball: int = random.randint(1, 25)
        return num1, num2, num3, num4, num5, mega_ball

    Let’s go through the code line by line:

    1. The function returns a tuple of Integers. The number of Integers range from three to six.
    2. Depending on the game, I either allow duplicate random numbers or use a range of random numbers to prevent duplicates.
    3. Each random number is assigned to a different variable.
    4. If the game has a specific lottery ball (like the Mega Ball in the previous code snippet) I assign a different random number to that variable.
    5. Finally, I return all the Integers from the function.
    6. The main.py file displays the numbers returned from the function:
    @app.route("/games/cash3.html", methods=["GET", "POST"])
    def cash3_game() -> render_template:
        """Displays the cash3.html template.
        :return: Cash 3 page displaying the three random numbers.
        """
        num1, num2, num3 = cash3()
        return render_template("/games/cash3.html", num1=num1, num2=num2, num3=num3)
    
    ...
    
    @app.route("/games/mega-millions.html", methods=["GET", "POST"])
    def mega_millions_game() -> render_template:
        """Displays the mega-millions.html template.
        :return: Mega Millions page displaying the six random numbers.
        """
        num1, num2, num3, num4, num5, mega = mega_millions()
        return render_template("/games/mega-millions.html", num1=num1, num2=num2, num3=num3, num4=num4, num5=num5,
                               mega=mega)

    The main.py file contains all the routes (or links) to the specific game’s page. This is the Flask code hard at work!

    I include the numbers on the game’s page template as so:

    ...
    
    <div class="container game-container">
        <div class="row">
          <div class="col">
            <h1 class="game-title">Cash3</h1>
            <h2 class="lotto-num-short">{{ num1 }} - {{ num2 }} - {{ num3 }}</h2>
            <form action="cash3.html" method="post">
              <button class="btn btn-lg game-btn" name="regenerate" type="submit"
                onclick="{{ num1 }} {{ num2 }} {{ num3 }}">Click-Go-Lotto Again</button>
            </form>
          </div>
        </div>
      </div>
    
    ...

    If users wants to generate a new set of numbers they click the button on screen, which runs the specific function again.

    My Future Plans For This Application

    I don’t have any major plans to modify the code for Click-Go-Lotto. It’s pretty much complete, unless a lottery game changes.

  • Learning Python From Books

    Learning Python From Books

    Recently I switched from using YouTube to improve my Python skills to reading books. Yes, actual books printed on paper. No digital or ebooks (even though there’s nothing wrong with using those, but I prefer reading from physical books). Why did I make this change to learning Python from books? Did I not like learning from video content? Should you use a book to learn a programming language? What are the pros and cons of my decision? I’ll answer those questions in this post.

    Why I’m Learning Python From Books

    I’m learning Python from books because I found myself not actually absorbing the material while watching videos on YouTube. I think this is a problem others run into, but don’t realize. Which is why they have trouble implementing what they learned into a project.

    I didn’t have a problem with focusing on the content, as the presenter was engaging. And I didn’t suffer from distractions, like from my phone, because when it’s study time I make sure to only focus on my study material. However, I found myself just watching the videos play without taking any notes. I became a watcher fully, not inspired to follow along with the presenter as that person typed on the screen.

    I would go back and rewatch the video to try to absorb the material. Sometimes I would even code along with the presenter using my code editor (usually PyCharm). Alas, those actions didn’t help me as much as I wanted. Thus, I turned to books.

    I got the idea to use a programming book from a YouTube video funny enough! As an avid fiction book reader I’m not sure why I didn’t think of this avenue myself. I’ve browsed books in the Technology section at bookstores in the past.

    I used Gemini to ask for Python programming book recommendations. Then I compared those to recommendations I got from YouTube searches. I finally purchased the following books:

    I’m currently reading Python Crash Course and will review it later on my website. So far it’s a great beginner book, and I’ve learned quite a bit from the author.

    Should You Use A Book To Learn Programming?

    While I’m enjoying learning Python from books, you, the reader, may not. Ask yourself these questions:

    • Do you enjoy the act of reading from a physical or digital book?
    • Reading requires time and focus. Do you have the ability to dedicate those?
    • Do you have the money to purchase books, or access to a library?

    If you can answer yes to all three, then I suggest you go ahead and use books. However, if you answer no to one of those questions you can still try to use a book, but I highly suggest checking one out from your local library to save on costs. Or you can try to find a used books at a substantial discount. This way if you don’t like using programming books then you won’t be out much money.

    Another con of using programming books is they can contain outdated information. Thus, it’s important to find a book (or even a book series) that’s updated at least yearly. Or if the book isn’t updated itself, the publisher or author provides online resources (like a PDF file) containing updated information for you to download.

  • Easy Home Directory Backup: An Introduction

    Easy Home Directory Backup: An Introduction

    Over my Christmas vacation I released a new technical project in its own repo onto my GitHub profile: Easy Home Directory Backup. It’s a Command Line Interface (CLI) program that runs on any Linux distribution in the Terminal. It performs a local backup of the user’s Home Directory. I developed the program in the Python programming language. In this post I will discuss the following topics about this web application:

    1. Why I created it
    2. The code running it
    3. My future plans for the application

    Why I Created Easy Home Directory Backup

    I created Easy Home Directory Backup because I wanted to improve upon a Python script I made to back up specific directories in my Home Directory. That script is also on my GitHub account which you can view here. I used a separate disk installed in my computer to hold the backups.

    While the script works great for me, I wanted to see if I could enhance it to work for other users of Linux distributions. Finally, I developed the application to continue on my current path to improve my Python programming skill-set.

    The Code Running Easy Home Directory Backup To Validate User Input

    During my design process for the program I decided to run the program in the Terminal using the Command Line Interface (CLI). I also wanted the user to provide the path of the local backup device. This imposed a problem I had to protect against: Improper user input.

    A user could input gibberish or a different value than what the program required. To keep the program from crashing, and inform the user of their mistake, I used “Try/Except” statements for user input. Here’s an truncated example from the main.py file:

    try:
            menu_choice: int = int(input("Enter your choice here: "))
            if menu_choice == 0:
                clear_screen()
                main_menu()
            elif menu_choice == 1:
                full_backup()
            elif menu_choice == 2:
                partial_backup()
            else:
                print()
                print(center_text("*" * 80))
                print(center_text("!! Enter either 0, 1, or 2 !!"))
                print()
                print(center_text(" -- The menu will reappear in a few seconds -- "))
                print(center_text("*" * 80))
                sleep_print()
                clear_screen()
                one_time_backup()
        except ValueError:
            print()
            print(center_text("*" * 80))
            print(center_text("!! This menu only accepts numbers !!"))
            print()
            print(center_text("-- The menu will reappear in a few seconds --"))
            print(center_text("*" * 80))
            sleep_print()
            clear_screen()
            one_time_backup()

    If a user entered a “4” the Else clause would fire off and a message would appear in the Terminal alert the user of their error. Thus, the user would have another chance to input the correct information in the menu. However, if the user entered an “a” that would fire off the Except statement regarding a ValueError. Again, a message appears in the Terminal informing the user to only enter numbers. Finally, the menu appears again.

    Not only did I have to validate user input, I had to validate if the path to the backup device was valid. Well, not only valid, but also a Linux-compatible file system, has read/write permissions, and mounted to name a few. I created a separate module (called validate_backup_device.py) to validate the path. I’ll explain a small portion of that code:

    def check_backup_device_permissions(backup_path: Path) -> bool:
            """Checks if the user-supplied backup device for read and write permissions.
            :param backup_path: The user-supplied path to the backup device.
            :return: True if the device has both read and write permissions, but False if it doesn't.
            """
            path_stats = os.stat(backup_path)
            mode = path_stats.st_mode
            return bool(mode & stat.S_IRUSR) and bool(mode & stat.S_IWUSR)

    In the function above I check the backup device has read/write permissions using the “stat” command to pull the file information about the backup device. Then I check the device’s mode to determine it has both read and write permissions. That’s what the “S_IRUSR” and the “S_IWUSR” options mean. The function returns True if both are correct, but False if not.

    I then run the function in one of the many conditional statements listed in the module:

    elif not check_backup_device_permissions(path):
            print()
            print(center_text(f"Backup path {path} doesn't have read/write permissions."))
            print(center_text("Please add the read/write permissions to the backup device and run this program again."))
            sleep_print()
            print()
            print(center_text("*" * 80))
            print(center_text("!! Program exited !!"))
            print(center_text("*" * 80))
            print()
            sys.exit()

    If the function returns False then the message displays in the Terminal about why the program cannot continue, and gives the user a suggestion on how to resolve the issue.

    This is just a snippet of code running the program. Please go through the repo to review the entire codebase.

    The Code Running The Backup

    I developed the program around the “rsync” command to perform the backups. however, I had to change the module I used in my script to call/run the command in the program. Before I used the “os.system” module, but during my research using Gemini I found out using the “subprocess.run” module was better to use. Because it offers secure command execution. Here’s what that code looks like:

    """rsync options
                        a = Archive
                        z = Compression
                        """
                        subprocess.run(["rsync", "-az",
                                        f"--log-file={path}/easy_home_directory_backup_{formatted_today_date}_log_file",
                                        home_dir_path, backup_device_path])

    That snippet of code performs a full backup of the user’s Home Directory. (The program also offers an option for a partial backup.) The flags in the code both archives and compresses the data. Finally, it creates a log file using a specific path name, including the date of the backup, and contains a list of all the files in the directories.

    My Future Plans For This Application

    I do want to add the ability for users to schedule a backup using crontab. However, I will have to work on implementing this feature as a user can only have one crontab. Thus, my code would need to append the file.

    Another feature I’m thinking about creating is allow users to delete the backups they create with the program. Again, that requires more testing.

    Once I enable one or more of those features I’ll post an update on my website.