phy3

Python Programming: Intermediate to Advanced Guide

Python Programming

Intermediate to Advanced Guide

A comprehensive resource for Python students

1. Exception Handling

What are Exceptions?

Exceptions are events that occur during program execution that disrupt the normal flow of instructions. They arise from various issues like invalid input, file errors, network problems, or logical mistakes.

Exception Flow Diagram

Program Start Program End try: Code that might raise exception except: Handle the exception finally: Clean-up code Exception Error occurs

Basic Exception Handling Structure

Basic Try-Except Structure
try:
    # Code that might raise an exception
    result = 10 / 0  # This will raise a ZeroDivisionError
except ZeroDivisionError:
    # Code to handle the specific exception
    print("Error: Division by zero!")
except (TypeError, ValueError) as e:
    # Handle multiple exception types and capture the exception object
    print(f"Error occurred: {e}")
except Exception as e:
    # Catch any other exceptions that weren't caught above
    print(f"Unexpected error: {e}")
else:
    # Runs only if no exceptions were raised
    print("Division operation successful!")
finally:
    # Always executes, regardless of whether an exception occurred
    print("Cleanup code here - always executes")

Memory Tip

Think of exception handling like a safety net in a circus:

  • try = The daring acrobat attempting a trick
  • except = The safety net that catches falls
  • else = The celebration when the trick succeeds
  • finally = The cleanup crew that comes in regardless

Remember the order with "TEEN": Try, Except, Else, aNd finally.

Common Built-in Exceptions

Exception Description Example
ZeroDivisionError Raised when division by zero occurs 10 / 0
TypeError Raised when an operation is performed on an inappropriate type '2' + 2
ValueError Raised when a function receives an argument of the correct type but improper value int('abc')
FileNotFoundError Raised when a file or directory is requested but doesn't exist open('missing.txt')
IndexError Raised when a sequence index is out of range my_list[99]
KeyError Raised when a dictionary key is not found my_dict['missing']

Creating Custom Exceptions

You can create your own exception classes by inheriting from the built-in Exception class or any of its subclasses. Custom exceptions help make your code more readable and provide more specific error handling.

Custom Exception Example
class InsufficientFundsError(Exception):
    """Exception raised when a withdrawal exceeds the available balance."""
    
    def __init__(self, balance, amount, message="Insufficient funds"):
        self.balance = balance
        self.amount = amount
        self.message = f"{message}: Tried to withdraw {amount} from balance of {balance}"
        super().__init__(self.message)

class BankAccount:
    def __init__(self, balance=0):
        self.balance = balance
        
    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientFundsError(self.balance, amount)
        self.balance -= amount
        return self.balance

# Using the custom exception
try:
    account = BankAccount(100)
    account.withdraw(150)
except InsufficientFundsError as e:
    print(e)  # This will print the custom error message
    # Additional handling specific to this type of error

Best Practices

  • Be specific with exception types - catch only what you can handle
  • Use multiple except blocks for different types of exceptions
  • Avoid bare except statements (without specifying the exception type)
  • Use finally blocks for cleanup code like closing files or network connections
  • Create custom exceptions for domain-specific errors
  • Include useful information in exception messages

Context Managers with try-except

Context managers (using the with statement) provide a clean way to handle resource acquisition and release, reducing the need for try-finally blocks.

Context Managers Example
# Without context manager
try:
    file = open('data.txt', 'r')
    content = file.read()
    # Process content
finally:
    file.close()  # Ensure the file is closed even if an exception occurs

# With context manager - much cleaner
try:
    with open('data.txt', 'r') as file:
        content = file.read()
        # Process content
        # File is automatically closed when the block exits
except FileNotFoundError:
    print("The file does not exist")
except PermissionError:
    print("You don't have permission to read this file")

2. Regular Expressions

What are Regular Expressions?

Regular expressions (regex or regexp) are powerful sequences of characters that define a search pattern. They are used for pattern matching with strings and for find-and-replace operations. In Python, regular expressions are implemented through the re module.

Common Regex Patterns

Pattern Description Example
\d Matches any digit (0-9) \d+ matches "42", "123", etc.
\w Matches any alphanumeric character and underscore \w+ matches "hello_world", "Python3", etc.
\s Matches any whitespace character \s+ matches spaces, tabs, newlines
[...] Matches any character in the brackets [aeiou] matches any vowel
* Matches 0 or more of the preceding character ab*c matches "ac", "abc", "abbc", etc.
+ Matches 1 or more of the preceding character ab+c matches "abc", "abbc", but not "ac"
? Matches 0 or 1 of the preceding character colou?r matches "color" and "colour"
{n} Matches exactly n occurrences of the preceding character \d{3} matches exactly 3 digits
^ Matches the start of a string ^Python matches strings starting with "Python"
$ Matches the end of a string Python$ matches strings ending with "Python"

Memory Tip

Remember regex characters with these mnemonics:

  • \d, \w, \s = "digits, words, spaces"
  • *, +, ? = "any, at least one, maybe"
  • ^ and $ = "starts and ends" (think of them as the bookends of your text)
  • [...] = "shopping cart" (you're selecting specific items from many choices)

Basic Regex Functions in Python

Common regex functions
import re

text = "Contact us at info@example.com or support@python.org"

# Search for the first match
match = re.search(r'\w+@\w+\.\w+', text)
if match:
    print("First email found:", match.group())  # Output: info@example.com

# Find all matches
all_emails = re.findall(r'\w+@\w+\.\w+', text)
print("All emails:", all_emails)  # Output: ['info@example.com', 'support@python.org']

# Replace matches
new_text = re.sub(r'\w+@\w+\.\w+', '[EMAIL REDACTED]', text)
print("Redacted text:", new_text)
# Output: Contact us at [EMAIL REDACTED] or [EMAIL REDACTED]

# Split by pattern
parts = re.split(r'[@\.]', 'info@example.com')
print("Split result:", parts)  # Output: ['info', 'example', 'com']

# Using match to check if pattern is at the beginning of the string
if re.match(r'Contact', text):
    print("Text starts with 'Contact'")  # This will print

Regex Groups and Capture

Regex groups allow you to extract specific parts of a match using parentheses. They're useful for parsing structured text.

Using regex groups
import re

# Parsing a date in format MM-DD-YYYY
date_string = "Today's date is 12-25-2023"
date_pattern = r'(\d{2})-(\d{2})-(\d{4})'

match = re.search(date_pattern, date_string)
if match:
    month, day, year = match.groups()
    print(f"Month: {month}, Day: {day}, Year: {year}")
    # Output: Month: 12, Day: 25, Year: 2023
    
    # Access specific group by number
    print(f"Year: {match.group(3)}")  # Output: Year: 2023
    
    # The entire match
    print(f"Full date: {match.group(0)}")  # Output: Full date: 12-25-2023

# Named groups for better readability
pattern = r'(?P\d{2})-(?P\d{2})-(?P\d{4})'
match = re.search(pattern, date_string)
if match:
    print(f"Using named groups - Month: {match.group('month')}, Year: {match.group('year')}")
    # Output: Using named groups - Month: 12, Year: 2023

Common Regex Use Cases

Practical regex examples
import re

# Email validation
def is_valid_email(email):
    pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
    return bool(re.match(pattern, email))

print(is_valid_email("user@example.com"))  # True
print(is_valid_email("invalid-email"))     # False

# Phone number formatting (US format)
def format_phone_number(number):
    # Remove all non-digit characters
    digits = re.sub(r'\D', '', number)
    if len(digits) == 10:
        # Format as (XXX) XXX-XXXX
        return re.sub(r'(\d{3})(\d{3})(\d{4})', r'(\1) \2-\3', digits)
    return "Invalid phone number"

print(format_phone_number("1234567890"))  # (123) 456-7890
print(format_phone_number("123-456-7890"))  # (123) 456-7890

# URL extraction
def extract_urls(text):
    pattern = r'https?://[\w\.-]+\.\w+[\w/\.-]*'
    return re.findall(pattern, text)

text = "Visit our website at https://www.example.com or https://python.org for more info"
print(extract_urls(text))  # ['https://www.example.com', 'https://python.org']

# Password validation
def is_strong_password(password):
    # Check for minimum length (8 characters)
    if len(password) < 8:
        return False
    
    # Check for at least one lowercase letter
    if not re.search(r'[a-z]', password):
        return False
    
    # Check for at least one uppercase letter
    if not re.search(r'[A-Z]', password):
        return False
    
    # Check for at least one digit
    if not re.search(r'\d', password):
        return False
    
    # Check for at least one special character
    if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
        return False
    
    return True

print(is_strong_password("Weak123"))  # False
print(is_strong_password("StrongP@ssword123"))  # True

Best Practices

  • Use raw strings (r"...") for regex patterns to avoid backslash confusion
  • Compile regex patterns that are used multiple times with re.compile() for better performance
  • Use named groups ((?P<name>pattern)) for complex patterns to improve readability
  • Test regex patterns with tools like regex101.com before implementing them
  • Keep patterns as simple as possible for maintainability
  • Use regex comments with the re.VERBOSE flag for complex patterns

Regex Flags

Regex flags modify how the pattern is interpreted and matched. They can be specified as an optional parameter to most regex functions.

Flag Description Example
re.IGNORECASE or re.I Case-insensitive matching re.search('python', 'PYTHON', re.I)
re.MULTILINE or re.M ^ and $ match at beginning/end of each line re.findall('^start', text, re.M)
re.DOTALL or re.S . matches newline characters as well re.search('start.*end', text, re.S)
re.VERBOSE or re.X Allows you to write clearer patterns with comments and whitespace See example below
VERBOSE flag example
import re

# Complex pattern for email with comments
email_pattern = re.compile(r'''
    ^                   # Start of string
    [\w\.-]+            # Username (alphanumeric, dots, hyphens)
    @                   # @ symbol
    [\w\.-]+            # Domain name
    \.                  # Dot
    [a-zA-Z]{2,}        # Top-level domain (2+ letters)
    $                   # End of string
''', re.VERBOSE)

# Test the pattern
emails = ['user@example.com', 'invalid@email', 'another.user@sub.domain.co.uk']
for email in emails:
    if email_pattern.match(email):
        print(f"{email} is valid")
    else:
        print(f"{email} is invalid")

# Multiple flags can be combined using the | operator
pattern = re.compile(r'python', re.IGNORECASE | re.MULTILINE)

3. Working with APIs

What are APIs?

API (Application Programming Interface) is a set of rules and protocols that allows different software applications to communicate with each other. Web APIs enable programmatic access to services and data from external systems over the internet.

API Communication Flow

Your Python App HTTP Request API External Service Internal Request JSON Response Data Authentication Input Parameters endpoint, query params, headers, body data Response Data JSON, XML, or other formats

Making API Requests with Python

The most popular library for making HTTP requests in Python is requests. It simplifies working with APIs by providing a clean, intuitive interface.

Basic API Requests
import requests

# Basic GET request
response = requests.get('https://api.example.com/data')
print(f"Status code: {response.status_code}")
print(f"Response content: {response.text}")

# GET request with parameters
params = {
    'q': 'python',
    'limit': 10
}
response = requests.get('https://api.example.com/search', params=params)
print(f"URL with parameters: {response.url}")

# POST request with JSON data
data = {
    'name': 'John Doe',
    'email': 'john@example.com'
}
response = requests.post('https://api.example.com/users', json=data)

# Request with headers (e.g., for authentication)
headers = {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
    'Content-Type': 'application/json'
}
response = requests.get('https://api.example.com/protected-resource', headers=headers)

# Other HTTP methods
put_response = requests.put('https://api.example.com/users/1', json={'name': 'Updated Name'})
delete_response = requests.delete('https://api.example.com/users/1')

Handling API Responses

Processing API responses
import requests

# Make a request to a JSON API
response = requests.get('https://jsonplaceholder.typicode.com/posts/1')

# Check if the request was successful
if response.status_code == 200:
    # Parse JSON response
    data = response.json()
    print(f"Post title: {data['title']}")
    print(f"Post body: {data['body']}")
else:
    print(f"Error: {response.status_code}")
    print(f"Error message: {response.text}")

# Working with response headers
print(f"Content type: {response.headers['Content-Type']}")
print(f"Response headers: {response.headers}")

# Getting binary content (e.g., for downloading images)
image_response = requests.get('https://example.com/image.jpg')
if image_response.status_code == 200:
    with open('downloaded_image.jpg', 'wb') as f:
        f.write(image_response.content)
    print("Image downloaded successfully")

# Handling different status codes
def handle_response(response):
    status = response.status_code
    
    if status == 200:
        return response.json()
    elif status == 400:
        print("Bad request - check your parameters")
    elif status == 401:
        print("Unauthorized - authentication required")
    elif status == 403:
        print("Forbidden - you don't have permission")
    elif status == 404:
        print("Resource not found")
    elif status == 429:
        print("Too many requests - rate limit exceeded")
    elif status >= 500:
        print("Server error - try again later")
    
    return None

Authentication Methods

APIs often require authentication to access protected resources. Here are common authentication methods:

Auth Method Description Implementation
API Key Simple string provided as a parameter or header headers = {'X-API-Key': 'your_api_key'}
Basic Auth Username and password encoded in base64 requests.get(url, auth=('username', 'password'))
Bearer Token JWT or OAuth token provided in Authorization header headers = {'Authorization': 'Bearer your_token'}
OAuth 2.0 Open standard for access delegation Use requests-oauthlib library
OAuth 2.0 Example with requests-oauthlib
from requests_oauthlib import OAuth2Session

# OAuth2 client credentials flow example
client_id = 'your_client_id'
client_secret = 'your_client_secret'
token_url = 'https://provider.com/oauth2/token'

# Create OAuth2 session
oauth = OAuth2Session(client_id=client_id)

# Get token
token = oauth.fetch_token(
    token_url=token_url,
    client_id=client_id,
    client_secret=client_secret
)

# Make authenticated request
response = oauth.get('https://api.example.com/resource')
print(response.json())

Error Handling and Retries

Working with external APIs requires robust error handling and retry strategies to handle temporary failures.

Error handling and retries
import requests
import time
from requests.exceptions import RequestException, Timeout, HTTPError

def fetch_with_retry(url, max_retries=3, backoff_factor=0.5, timeout=10):
    """
    Make a GET request with retry logic.
    
    Args:
        url: The URL to request
        max_retries: Maximum number of retry attempts
        backoff_factor: Factor to determine exponential backoff time
        timeout: Request timeout in seconds
        
    Returns:
        Response object or None if all retries fail
    """
    retries = 0
    while retries <= max_retries:
        try:
            response = requests.get(url, timeout=timeout)
            response.raise_for_status()  # Raise exception for 4XX/5XX status codes
            return response
        except Timeout:
            wait_time = backoff_factor * (2 ** retries)
            print(f"Request timed out. Retrying in {wait_time} seconds...")
        except HTTPError as e:
            # Don't retry for client errors (4XX) except 429 (too many requests)
            if 400 <= e.response.status_code < 500 and e.response.status_code != 429:
                print(f"Client error: {e}")
                return e.response
            wait_time = backoff_factor * (2 ** retries)
            print(f"HTTP error: {e}. Retrying in {wait_time} seconds...")
        except RequestException as e:
            wait_time = backoff_factor * (2 ** retries)
            print(f"Request failed: {e}. Retrying in {wait_time} seconds...")
        
        retries += 1
        if retries <= max_retries:
            time.sleep(wait_time)
    
    print(f"Failed after {max_retries} retries")
    return None

# Usage
response = fetch_with_retry('https://api.example.com/data')
if response and response.status_code == 200:
    data = response.json()
    print("Data retrieved successfully:", data)
else:
    print("Failed to retrieve data")

Handling Rate Limits

Many APIs impose rate limits to prevent abuse. It's important to respect these limits and implement proper handling.

Working with rate limits
import requests
import time

class RateLimitedAPI:
    def __init__(self, base_url, requests_per_minute=60):
        self.base_url = base_url
        self.min_interval = 60.0 / requests_per_minute  # Minimum seconds between requests
        self.last_request_time = 0
    
    def _wait_if_needed(self):
        """Wait to comply with rate limit"""
        elapsed = time.time() - self.last_request_time
        if elapsed < self.min_interval:
            sleep_time = self.min_interval - elapsed
            time.sleep(sleep_time)
    
    def make_request(self, endpoint, method='get', **kwargs):
        """Make a rate-limited request to the API"""
        self._wait_if_needed()
        
        url = f"{self.base_url}/{endpoint}"
        response = None
        
        try:
            if method.lower() == 'get':
                response = requests.get(url, **kwargs)
            elif method.lower() == 'post':
                response = requests.post(url, **kwargs)
            elif method.lower() == 'put':
                response = requests.put(url, **kwargs)
            elif method.lower() == 'delete':
                response = requests.delete(url, **kwargs)
            else:
                raise ValueError(f"Unsupported HTTP method: {method}")
            
            # Check for rate limit headers and adjust if needed
            if 'X-RateLimit-Remaining' in response.headers:
                remaining = int(response.headers['X-RateLimit-Remaining'])
                if remaining <= 1:
                    print("Rate limit almost reached, slowing down requests")
                    self.min_interval *= 2  # Double the wait time
            
            # Handle 429 (Too Many Requests) status
            if response.status_code == 429 and 'Retry-After' in response.headers:
                retry_after = int(response.headers['Retry-After'])
                print(f"Rate limit exceeded. Waiting for {retry_after} seconds")
                time.sleep(retry_after)
                # Retry the request
                return self.make_request(endpoint, method, **kwargs)
                
        finally:
            self.last_request_time = time.time()
        
        return response

# Example usage
api = RateLimitedAPI('https://api.example.com', requests_per_minute=30)

# Make multiple requests
for i in range(10):
    response = api.make_request(f'items/{i}')
    print(f"Request {i}: Status {response.status_code}")

Best Practices for API Usage

  • Store API keys and sensitive information in environment variables, not in code
  • Implement proper error handling for failed requests
  • Add exponential backoff for retries to avoid overwhelming the API
  • Respect rate limits to prevent being blocked
  • Use appropriate HTTP methods (GET, POST, PUT, DELETE) according to the operation
  • Cache responses when appropriate to reduce API calls
  • Implement proper authentication and keep tokens secure
  • Validate input parameters before sending them to the API

Building Your Own API

As you become more comfortable with using APIs, you might want to create your own. Here's a simple example using Flask:

Creating a simple REST API with Flask
from flask import Flask, request, jsonify

app = Flask(__name__)

# In-memory database for demonstration
books = [
    {"id": 1, "title": "Python Crash Course", "author": "Eric Matthes"},
    {"id": 2, "title": "Automate the Boring Stuff with Python", "author": "Al Sweigart"}
]

# GET all books
@app.route('/api/books', methods=['GET'])
def get_books():
    return jsonify(books)

# GET a specific book
@app.route('/api/books/', methods=['GET'])
def get_book(book_id):
    book = next((book for book in books if book['id'] == book_id), None)
    if book:
        return jsonify(book)
    return jsonify({"error": "Book not found"}), 404

# POST a new book
@app.route('/api/books', methods=['POST'])
def add_book():
    if not request.json or 'title' not in request.json or 'author' not in request.json:
        return jsonify({"error": "Title and author are required"}), 400
    
    new_book = {
        "id": max(book['id'] for book in books) + 1 if books else 1,
        "title": request.json['title'],
        "author": request.json['author']
    }
    books.append(new_book)
    return jsonify(new_book), 201

# PUT (update) a book
@app.route('/api/books/', methods=['PUT'])
def update_book(book_id):
    book = next((book for book in books if book['id'] == book_id), None)
    if not book:
        return jsonify({"error": "Book not found"}), 404
    
    if not request.json:
        return jsonify({"error": "No data provided"}), 400
    
    book['title'] = request.json.get('title', book['title'])
    book['author'] = request.json.get('author', book['author'])
    return jsonify(book)

# DELETE a book
@app.route('/api/books/', methods=['DELETE'])
def delete_book(book_id):
    global books
    book = next((book for book in books if book['id'] == book_id), None)
    if not book:
        return jsonify({"error": "Book not found"}), 404
    
    books = [b for b in books if b['id'] != book_id]
    return jsonify({"message": f"Book {book_id} deleted"})

if __name__ == '__main__':
    app.run(debug=True)

Memory Tip for API Concepts

Remember API interactions with the "CRUD" acronym, which maps to HTTP methods:

  • Create = POST
  • Read = GET
  • Update = PUT/PATCH
  • Delete = DELETE

And remember the core components of API requests with "HUMP":

  • Headers (metadata, auth)
  • URL (endpoint)
  • Method (GET, POST, etc.)
  • Parameters/Payload (data)

4. Intermediate Projects

Now that we've covered key Python concepts, let's apply this knowledge to real-world projects. These intermediate projects will help you consolidate your skills and build practical applications.

4.1 Language Detector using Tkinter and langdetect

This project combines GUI development with natural language processing to create an application that can detect the language of input text.

Key Concepts Used:

  • Tkinter for GUI development
  • Natural language processing with langdetect
  • Event handling
  • Exception handling

Project Implementation:

Language Detector Application
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox
from langdetect import detect, LangDetectException
import pycountry

class LanguageDetectorApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Language Detector")
        self.root.geometry("600x400")
        self.root.configure(bg="#f0f4f8")
        
        # Configure styles
        self.style = ttk.Style()
        self.style.configure("TButton", font=("Arial", 12), padding=10)
        self.style.configure("TLabel", font=("Arial", 12), padding=5, background="#f0f4f8")
        
        # Title label
        self.title_label = ttk.Label(
            root, 
            text="Language Detector", 
            font=("Arial", 18, "bold"),
            background="#f0f4f8"
        )
        self.title_label.pack(pady=10)
        
        # Instructions label
        self.instructions = ttk.Label(
            root, 
            text="Enter text below and click 'Detect Language'",
            background="#f0f4f8"
        )
        self.instructions.pack(pady=5)
        
        # Text input area
        self.text_input = scrolledtext.ScrolledText(
            root, 
            width=50, 
            height=10, 
            font=("Arial", 12),
            wrap=tk.WORD
        )
        self.text_input.pack(pady=10, padx=20)
        
        # Detect button
        self.detect_button = ttk.Button(
            root,
            text="Detect Language",
            command=self.detect_language
        )
        self.detect_button.pack(pady=10)
        
        # Result frame
        self.result_frame = ttk.Frame(root, padding="10")
        self.result_frame.pack(fill="x", padx=20)
        
        # Result label
        self.result_label = ttk.Label(
            self.result_frame,
            text="Detected Language: ",
            font=("Arial", 12, "bold"),
            background="#f0f4f8"
        )
        self.result_label.pack(side="left")
        
        # Language result
        self.language_result = ttk.Label(
            self.result_frame,
            text="None",
            font=("Arial", 12),
            background="#f0f4f8"
        )
        self.language_result.pack(side="left")
        
        # Language code result
        self.code_result = ttk.Label(
            self.result_frame,
            text="",
            font=("Arial", 12),
            background="#f0f4f8"
        )
        self.code_result.pack(side="left", padx=5)
    
    def detect_language(self):
        """Detect the language of the input text."""
        text = self.text_input.get("1.0", tk.END).strip()
        
        if not text:
            messagebox.showwarning("Empty Input", "Please enter some text to detect the language.")
            return
        
        try:
            # Detect language
            lang_code = detect(text)
            
            # Get full language name
            language_name = "Unknown"
            try:
                language = pycountry.languages.get(alpha_2=lang_code)
                if language:
                    language_name = language.name
            except:
                # Fallback for languages not in pycountry
                language_map = {
                    'zh-cn': 'Chinese (Simplified)',
                    'zh-tw': 'Chinese (Traditional)',
                    'en': 'English',
                    'es': 'Spanish',
                    'fr': 'French',
                    'de': 'German',
                    'ja': 'Japanese',
                    'ko': 'Korean',
                    'ru': 'Russian',
                    'ar': 'Arabic'
                }
                language_name = language_map.get(lang_code, f"Unknown ({lang_code})")
            
            # Update the result labels
            self.language_result.config(text=language_name)
            self.code_result.config(text=f"({lang_code})")
            
        except LangDetectException as e:
            messagebox.showerror("Detection Error", f"Error detecting language: {str(e)}")
        except Exception as e:
            messagebox.showerror("Error", f"An unexpected error occurred: {str(e)}")

if __name__ == "__main__":
    root = tk.Tk()
    app = LanguageDetectorApp(root)
    root.mainloop()

Requirements:

Install the required packages:

pip install langdetect pycountry

How It Works:

  1. The user enters text in the input area
  2. When the "Detect Language" button is clicked, the application sends the text to the langdetect library
  3. The detected language code is converted to a full language name using pycountry
  4. The result is displayed in the UI
  5. Error handling ensures the application doesn't crash when invalid input is provided

Extensions and Improvements:

  • Add confidence scores for multiple possible languages
  • Implement translation functionality
  • Add support for file input (detect language from text files)
  • Create a history feature to save previous detections

4.2 Text to Speech Converter

This project creates a text-to-speech application that converts written text into spoken words, with options to adjust voice, speed, and save the audio output.

Key Concepts Used:

  • Text-to-speech conversion with pyttsx3
  • GUI development with Tkinter
  • File handling for saving audio
  • Multi-threading for non-blocking UI

Project Implementation:

Text to Speech Converter
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox, filedialog
import pyttsx3
import threading
import os

class TextToSpeechApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Text to Speech Converter")
        self.root.geometry("650x500")
        self.root.configure(bg="#f5f5f5")
        
        # Initialize text-to-speech engine
        self.engine = pyttsx3.init()
        self.voices = {}
        self.populate_voices()
        
        # Default rate and volume
        self.rate = 150
        self.volume = 1.0
        
        # Create main frame
        self.main_frame = ttk.Frame(root, padding="20")
        self.main_frame.pack(fill="both", expand=True)
        
        # Title
        self.title_label = ttk.Label(
            self.main_frame,
            text="Text to Speech Converter",
            font=("Arial", 18, "bold")
        )
        self.title_label.pack(pady=10)
        
        # Text input area
        self.text_label = ttk.Label(
            self.main_frame,
            text="Enter text to convert:",
            font=("Arial", 12)
        )
        self.text_label.pack(anchor="w", pady=(10, 5))
        
        self.text_input = scrolledtext.ScrolledText(
            self.main_frame,
            width=60,
            height=10,
            font=("Arial", 12),
            wrap=tk.WORD
        )
        self.text_input.pack(pady=5, fill="both", expand=True)
        
        # Controls frame
        self.controls_frame = ttk.Frame(self.main_frame)
        self.controls_frame.pack(fill="x", pady=10)
        
        # Voice selection
        self.voice_label = ttk.Label(
            self.controls_frame,
            text="Voice:",
            font=("Arial", 12)
        )
        self.voice_label.grid(row=0, column=0, padx=5, pady=5, sticky="w")
        
        self.voice_var = tk.StringVar()
        self.voice_dropdown = ttk.Combobox(
            self.controls_frame,
            textvariable=self.voice_var,
            values=list(self.voices.keys()),
            width=20,
            state="readonly"
        )
        self.voice_dropdown.grid(row=0, column=1, padx=5, pady=5, sticky="w")
        if self.voices:
            self.voice_dropdown.current(0)
            self.voice_var.trace_add("write", self.on_voice_change)
        
        # Rate control
        self.rate_label = ttk.Label(
            self.controls_frame,
            text="Speed:",
            font=("Arial", 12)
        )
        self.rate_label.grid(row=0, column=2, padx=5, pady=5, sticky="w")
        
        self.rate_var = tk.IntVar(value=self.rate)
        self.rate_slider = ttk.Scale(
            self.controls_frame,
            from_=50,
            to=300,
            orient="horizontal",
            variable=self.rate_var,
            length=100,
            command=self.on_rate_change
        )
        self.rate_slider.grid(row=0, column=3, padx=5, pady=5)
        
        self.rate_value_label = ttk.Label(
            self.controls_frame,
            text=str(self.rate),
            width=3
        )
        self.rate_value_label.grid(row=0, column=4, padx=5, pady=5)
        
        # Volume control
        self.volume_label = ttk.Label(
            self.controls_frame,
            text="Volume:",
            font=("Arial", 12)
        )
        self.volume_label.grid(row=1, column=0, padx=5, pady=5, sticky="w")
        
        self.volume_var = tk.DoubleVar(value=self.volume)
        self.volume_slider = ttk.Scale(
            self.controls_frame,
            from_=0.0,
            to=1.0,
            orient="horizontal",
            variable=self.volume_var,
            length=100,
            command=self.on_volume_change
        )
        self.volume_slider.grid(row=1, column=1, padx=5, pady=5)
        
        self.volume_value_label = ttk.Label(
            self.controls_frame,
            text=f"{int(self.volume * 100)}%",
            width=4
        )
        self.volume_value_label.grid(row=1, column=2, padx=5, pady=5)
        
        # Buttons frame
        self.buttons_frame = ttk.Frame(self.main_frame)
        self.buttons_frame.pack(fill="x", pady=10)
        
        # Speak button
        self.speak_button = ttk.Button(
            self.buttons_frame,
            text="Speak",
            command=self.speak_text,
            width=15
        )
        self.speak_button.grid(row=0, column=0, padx=5, pady=5)
        
        # Stop button
        self.stop_button = ttk.Button(
            self.buttons_frame,
            text="Stop",
            command=self.stop_speaking,
            width=15
        )
        self.stop_button.grid(row=0, column=1, padx=5, pady=5)
        
        # Save as audio button
        self.save_button = ttk.Button(
            self.buttons_frame,
            text="Save as Audio",
            command=self.save_audio,
            width=15
        )
        self.save_button.grid(row=0, column=2, padx=5, pady=5)
        
        # Clear button
        self.clear_button = ttk.Button(
            self.buttons_frame,
            text="Clear Text",
            command=self.clear_text,
            width=15
        )
        self.clear_button.grid(row=0, column=3, padx=5, pady=5)
        
        # Status bar
        self.status_var = tk.StringVar(value="Ready")
        self.status_bar = ttk.Label(
            root,
            textvariable=self.status_var,
            relief=tk.SUNKEN,
            anchor=tk.W,
            padding=(5, 2)
        )
        self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        
        # Center the buttons
        self.buttons_frame.grid_columnconfigure((0, 1, 2, 3), weight=1)
    
    def populate_voices(self):
        """Populate the voices dictionary with available voices."""
        for voice in self.engine.getProperty('voices'):
            self.voices[f"{voice.name} ({voice.id})"] = voice.id
    
    def on_voice_change(self, *args):
        """Handle voice change event."""
        selected_voice = self.voice_var.get()
        if selected_voice in self.voices:
            self.engine.setProperty('voice', self.voices[selected_voice])
    
    def on_rate_change(self, *args):
        """Handle rate change event."""
        self.rate = self.rate_var.get()
        self.rate_value_label.config(text=str(self.rate))
        self.engine.setProperty('rate', self.rate)
    
    def on_volume_change(self, *args):
        """Handle volume change event."""
        self.volume = self.volume_var.get()
        self.volume_value_label.config(text=f"{int(self.volume * 100)}%")
        self.engine.setProperty('volume', self.volume)
    
    def speak_text(self):
        """Speak the entered text."""
        text = self.text_input.get("1.0", tk.END).strip()
        if not text:
            messagebox.showwarning("Empty Text", "Please enter some text to speak.")
            return
        
        # Update UI state
        self.status_var.set("Speaking...")
        self.speak_button.config(state="disabled")
        
        # Start speaking in a separate thread to avoid blocking the UI
        threading.Thread(target=self._speak_thread, args=(text,), daemon=True).start()
    
    def _speak_thread(self, text):
        """Thread function for speaking text."""
        try:
            self.engine.say(text)
            self.engine.runAndWait()
        except Exception as e:
            messagebox.showerror("Error", f"An error occurred: {str(e)}")
        finally:
            # Update UI state back to normal
            self.root.after(0, self._reset_ui)
    
    def _reset_ui(self):
        """Reset UI state after speaking."""
        self.status_var.set("Ready")
        self.speak_button.config(state="normal")
    
    def stop_speaking(self):
        """Stop current speech."""
        self.engine.stop()
        self.status_var.set("Stopped")
    
    def clear_text(self):
        """Clear the text input area."""
        self.text_input.delete("1.0", tk.END)
    
    def save_audio(self):
        """Save speech as an audio file."""
        text = self.text_input.get("1.0", tk.END).strip()
        if not text:
            messagebox.showwarning("Empty Text", "Please enter some text to save as audio.")
            return
        
        filetypes = [("WAV files", "*.wav"), ("All files", "*.*")]
        filename = filedialog.asksaveasfilename(
            title="Save Audio File",
            filetypes=filetypes,
            defaultextension=".wav"
        )
        
        if not filename:
            return  # User canceled
        
        self.status_var.set(f"Saving audio to {os.path.basename(filename)}...")
        self.save_button.config(state="disabled")
        
        # Start saving in a separate thread to avoid blocking the UI
        threading.Thread(target=self._save_thread, args=(text, filename), daemon=True).start()
    
    def _save_thread(self, text, filename):
        """Thread function for saving audio."""
        try:
            # Save speech to file (requires mpg123 for mp3 or similar)
            # This example just generates the speech without saving
            # For actual file saving, you'd need to use a library like gTTS
            messagebox.showinfo("Information", 
                                "The pyttsx3 library doesn't directly support saving to files.\n"
                                "To save audio files, consider using the gTTS library instead.")
            
            # If you had gTTS, it would look like:
            # from gtts import gTTS
            # tts = gTTS(text=text, lang='en')
            # tts.save(filename)
        except Exception as e:
            messagebox.showerror("Error", f"Failed to save audio: {str(e)}")
        finally:
            # Update UI state back to normal
            self.root.after(0, self._reset_save_ui)
    
    def _reset_save_ui(self):
        """Reset UI state after saving."""
        self.status_var.set("Ready")
        self.save_button.config(state="normal")

if __name__ == "__main__":
    root = tk.Tk()
    app = TextToSpeechApp(root)
    root.mainloop()

Requirements:

Install the required packages:

pip install pyttsx3

Note

For fully functional audio file saving, you should use additional libraries like gTTS (Google Text-to-Speech). The example above shows where this would be implemented.

How It Works:

  1. The application initializes the pyttsx3 engine and retrieves available voices
  2. The user enters text and can adjust voice, speaking rate, and volume
  3. When the "Speak" button is clicked, the text is passed to the TTS engine in a separate thread
  4. Threading prevents the UI from freezing during speech
  5. The user can stop the speech at any time

Extensions and Improvements:

  • Add support for multiple languages using different TTS engines
  • Implement audio file saving with gTTS or another library
  • Add text-to-speech highlighting to show current reading position
  • Create a history feature to save frequently used phrases
  • Implement a feature to read text from PDF or web pages

4.3 Alarm Clock

This project creates a feature-rich alarm clock application with a digital display, multiple alarms, snooze functionality, and customizable alarm sounds.

Key Concepts Used:

  • Time handling in Python
  • Multithreading for alarm monitoring
  • Event-driven programming
  • Audio playback
  • Data persistence for saving alarms

Project Implementation:

Alarm Clock Application
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import time
import datetime
import threading
import json
import os
import pygame  # For playing alarm sounds

class AlarmClock:
    def __init__(self, root):
        self.root = root
        self.root.title("Alarm Clock")
        self.root.geometry("500x600")
        self.root.configure(bg="#f0f0f0")
        
        # Initialize pygame mixer for audio
        pygame.mixer.init()
        
        # Default alarm sound
        self.default_alarm_sound = "alarm.mp3"  # You should include this file or find a default
        
        # Alarm data structure
        self.alarms = []
        self.active_alarm = None
        self.snooze_time = 5  # Default snooze time in minutes
        
        # Create and configure styles
        self.style = ttk.Style()
        self.style.configure("TButton", font=("Arial", 12))
        self.style.configure("TLabel", font=("Arial", 12), background="#f0f0f0")
        
        # Main frame
        self.main_frame = ttk.Frame(root, padding="20")
        self.main_frame.pack(fill="both", expand=True)
        
        # Clock display
        self.clock_frame = ttk.Frame(self.main_frame)
        self.clock_frame.pack(fill="x", pady=10)
        
        self.time_display = ttk.Label(
            self.clock_frame,
            font=("Digital-7", 48, "bold"),
            background="#000000",
            foreground="#00ff00",
            text="00:00:00",
            anchor="center",
            padding=10
        )
        self.time_display.pack(fill="x")
        
        self.date_display = ttk.Label(
            self.clock_frame,
            font=("Arial", 14),
            text="",
            anchor="center"
        )
        self.date_display.pack(fill="x", pady=5)
        
        # Alarm setup frame
        self.setup_frame = ttk.LabelFrame(self.main_frame, text="Set Alarm", padding=10)
        self.setup_frame.pack(fill="x", pady=10)
        
        # Time selection
        self.time_frame = ttk.Frame(self.setup_frame)
        self.time_frame.pack(fill="x", pady=5)
        
        # Hour selection
        ttk.Label(self.time_frame, text="Hour:").grid(row=0, column=0, padx=5)
        self.hour_var = tk.StringVar()
        self.hour_combo = ttk.Combobox(
            self.time_frame,
            textvariable=self.hour_var,
            values=[f"{i:02d}" for i in range(24)],
            width=5,
            state="readonly"
        )
        self.hour_combo.grid(row=0, column=1, padx=5)
        self.hour_combo.current(datetime.datetime.now().hour)
        
        # Minute selection
        ttk.Label(self.time_frame, text="Minute:").grid(row=0, column=2, padx=5)
        self.minute_var = tk.StringVar()
        self.minute_combo = ttk.Combobox(
            self.time_frame,
            textvariable=self.minute_var,
            values=[f"{i:02d}" for i in range(60)],
            width=5,
            state="readonly"
        )
        self.minute_combo.grid(row=0, column=3, padx=5)
        self.minute_combo.current(datetime.datetime.now().minute)
        
        # Alarm name
        self.name_frame = ttk.Frame(self.setup_frame)
        self.name_frame.pack(fill="x", pady=5)
        
        ttk.Label(self.name_frame, text="Alarm Name:").pack(side="left", padx=5)
        self.alarm_name_var = tk.StringVar(value="My Alarm")
        self.alarm_name_entry = ttk.Entry(
            self.name_frame,
            textvariable=self.alarm_name_var,
            width=25
        )
        self.alarm_name_entry.pack(side="left", padx=5, fill="x", expand=True)
        
        # Alarm sound
        self.sound_frame = ttk.Frame(self.setup_frame)
        self.sound_frame.pack(fill="x", pady=5)
        
        ttk.Label(self.sound_frame, text="Alarm Sound:").pack(side="left", padx=5)
        self.alarm_sound_var = tk.StringVar(value=self.default_alarm_sound)
        self.alarm_sound_entry = ttk.Entry(
            self.sound_frame,
            textvariable=self.alarm_sound_var,
            width=25
        )
        self.alarm_sound_entry.pack(side="left", padx=5, fill="x", expand=True)
        
        self.browse_button = ttk.Button(
            self.sound_frame,
            text="Browse",
            command=self.browse_alarm_sound
        )
        self.browse_button.pack(side="left", padx=5)
        
        # Days selection (for recurring alarms)
        self.days_frame = ttk.Frame(self.setup_frame)
        self.days_frame.pack(fill="x", pady=5)
        
        ttk.Label(self.days_frame, text="Repeat on:").pack(side="left", padx=5)
        
        self.days_vars = {}
        days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
        for day in days:
            self.days_vars[day] = tk.BooleanVar(value=False)
            ttk.Checkbutton(
                self.days_frame,
                text=day,
                variable=self.days_vars[day]
            ).pack(side="left")
        
        # Set alarm button
        self.set_button = ttk.Button(
            self.setup_frame,
            text="Set Alarm",
            command=self.set_alarm
        )
        self.set_button.pack(pady=10)
        
        # Alarms list frame
        self.list_frame = ttk.LabelFrame(self.main_frame, text="Alarms", padding=10)
        self.list_frame.pack(fill="both", expand=True, pady=10)
        
        # Scrollable frame for alarms
        self.canvas = tk.Canvas(self.list_frame)
        self.scrollbar = ttk.Scrollbar(self.list_frame, orient="vertical", command=self.canvas.yview)
        self.scrollable_frame = ttk.Frame(self.canvas)
        
        self.scrollable_frame.bind(
            "",
            lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))
        )
        
        self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        self.canvas.configure(yscrollcommand=self.scrollbar.set)
        
        self.canvas.pack(side="left", fill="both", expand=True)
        self.scrollbar.pack(side="right", fill="y")
        
        # Status bar
        self.status_var = tk.StringVar(value="Ready")
        self.status_bar = ttk.Label(
            root,
            textvariable=self.status_var,
            relief=tk.SUNKEN,
            anchor=tk.W,
            padding=(5, 2)
        )
        self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        
        # Load saved alarms
        self.load_alarms()
        
        # Start updating the clock
        self.update_clock()
        
        # Start the alarm checker thread
        self.alarm_thread = threading.Thread(target=self.check_alarms, daemon=True)
        self.alarm_thread.start()
    
    def update_clock(self):
        """Update the digital clock display."""
        current_time = time.strftime("%H:%M:%S")
        current_date = time.strftime("%A, %B %d, %Y")
        
        self.time_display.config(text=current_time)
        self.date_display.config(text=current_date)
        
        # Update every second
        self.root.after(1000, self.update_clock)
    
    def browse_alarm_sound(self):
        """Open file dialog to select alarm sound."""
        filetypes = [
            ("Audio files", "*.mp3 *.wav"),
            ("MP3 files", "*.mp3"),
            ("WAV files", "*.wav"),
            ("All files", "*.*")
        ]
        
        filename = filedialog.askopenfilename(
            title="Select Alarm Sound",
            filetypes=filetypes
        )
        
        if filename:
            self.alarm_sound_var.set(filename)
    
    def set_alarm(self):
        """Add a new alarm to the list."""
        try:
            # Get alarm time
            hour = int(self.hour_var.get())
            minute = int(self.minute_var.get())
            
            # Validate time
            if not (0 <= hour < 24 and 0 <= minute < 60):
                messagebox.showerror("Invalid Time", "Please enter a valid time.")
                return
            
            # Get other alarm properties
            name = self.alarm_name_var.get()
            sound = self.alarm_sound_var.get()
            
            # Get selected days
            days = [day for day, var in self.days_vars.items() if var.get()]
            
            # Create alarm object
            alarm = {
                "id": time.time(),  # Use timestamp as unique ID
                "name": name,
                "hour": hour,
                "minute": minute,
                "sound": sound,
                "days": days,
                "enabled": True
            }
            
            # Add to alarms list
            self.alarms.append(alarm)
            
            # Update the display
            self.refresh_alarm_list()
            
            # Save alarms
            self.save_alarms()
            
            # Show confirmation
            self.status_var.set(f"Alarm '{name}' set for {hour:02d}:{minute:02d}")
            
            # Reset form
            self.alarm_name_var.set("My Alarm")
            
        except ValueError:
            messagebox.showerror("Invalid Input", "Please enter valid numbers for hour and minute.")
    
    def refresh_alarm_list(self):
        """Update the display of alarms."""
        # Clear existing alarms display
        for widget in self.scrollable_frame.winfo_children():
            widget.destroy()
        
        # No alarms message
        if not self.alarms:
            ttk.Label(
                self.scrollable_frame,
                text="No alarms set. Use the form above to create an alarm.",
                padding=10
            ).pack()
            return
        
        # Sort alarms by time
        sorted_alarms = sorted(self.alarms, key=lambda a: (a["hour"], a["minute"]))
        
        # Add each alarm to the display
        for i, alarm in enumerate(sorted_alarms):
            frame = ttk.Frame(self.scrollable_frame, padding=5)
            frame.pack(fill="x", pady=2)
            
            # Add a line if not the first alarm
            if i > 0:
                separator = ttk.Separator(self.scrollable_frame, orient="horizontal")
                separator.pack(fill="x", pady=5)
            
            # Enabled status checkbox
            enabled_var = tk.BooleanVar(value=alarm["enabled"])
            ttk.Checkbutton(
                frame,
                variable=enabled_var,
                command=lambda a=alarm, v=enabled_var: self.toggle_alarm(a, v.get())
            ).pack(side="left")
            
            # Time and name
            days_text = f" [{', '.join(alarm['days'])}]" if alarm["days"] else ""
            ttk.Label(
                frame,
                text=f"{alarm['hour']:02d}:{alarm['minute']:02d} - {alarm['name']}{days_text}",
                font=("Arial", 12)
            ).pack(side="left", padx=5)
            
            # Delete button
            ttk.Button(
                frame,
                text="Delete",
                command=lambda a=alarm: self.delete_alarm(a)
            ).pack(side="right")
    
    def toggle_alarm(self, alarm, enabled):
        """Enable or disable an alarm."""
        for a in self.alarms:
            if a["id"] == alarm["id"]:
                a["enabled"] = enabled
                break
        
        self.save_alarms()
    
    def delete_alarm(self, alarm):
        """Remove an alarm from the list."""
        self.alarms = [a for a in self.alarms if a["id"] != alarm["id"]]
        self.refresh_alarm_list()
        self.save_alarms()
    
    def save_alarms(self):
        """Save alarms to a JSON file."""
        try:
            with open("alarms.json", "w") as f:
                json.dump(self.alarms, f)
        except Exception as e:
            messagebox.showerror("Error", f"Failed to save alarms: {str(e)}")
    
    def load_alarms(self):
        """Load alarms from a JSON file."""
        try:
            if os.path.exists("alarms.json"):
                with open("alarms.json", "r") as f:
                    self.alarms = json.load(f)
                self.refresh_alarm_list()
        except Exception as e:
            messagebox.showerror("Error", f"Failed to load alarms: {str(e)}")
    
    def check_alarms(self):
        """Check if any alarms should trigger."""
        while True:
            if self.active_alarm:
                # An alarm is already ringing
                time.sleep(1)
                continue
            
            now = datetime.datetime.now()
            current_time = (now.hour, now.minute)
            current_day = now.strftime("%a")
            
            for alarm in self.alarms:
                if not alarm["enabled"]:
                    continue
                
                alarm_time = (alarm["hour"], alarm["minute"])
                
                # Check if this alarm should ring now
                if current_time == alarm_time:
                    # For recurring alarms, check the day
                    if alarm["days"] and current_day not in alarm["days"]:
                        continue
                    
                    # Trigger the alarm
                    self.trigger_alarm(alarm)
                    break
            
            # Check every second
            time.sleep(1)
    
    def trigger_alarm(self, alarm):
        """Activate an alarm."""
        self.active_alarm = alarm
        self.root.after(0, self._show_alarm_dialog, alarm)
        
        # Play the sound
        try:
            sound_file = alarm["sound"]
            if not os.path.exists(sound_file):
                sound_file = self.default_alarm_sound
            
            pygame.mixer.music.load(sound_file)
            pygame.mixer.music.play(-1)  # Loop indefinitely
        except Exception as e:
            print(f"Error playing alarm sound: {e}")
    
    def _show_alarm_dialog(self, alarm):
        """Show the alarm dialog on the main thread."""
        dialog = tk.Toplevel(self.root)
        dialog.title("Alarm!")
        dialog.geometry("400x200")
        dialog.attributes('-topmost', True)
        
        # Make it stand out
        dialog.configure(bg="#ff9999")
        
        # Alarm message
        tk.Label(
            dialog,
            text=f"Time's up!",
            font=("Arial", 18, "bold"),
            bg="#ff9999"
        ).pack(pady=10)
        
        tk.Label(
            dialog,
            text=alarm["name"],
            font=("Arial", 14),
            bg="#ff9999"
        ).pack(pady=5)
        
        # Buttons frame
        buttons_frame = tk.Frame(dialog, bg="#ff9999")
        buttons_frame.pack(pady=20)
        
        # Dismiss button
        tk.Button(
            buttons_frame,
            text="Dismiss",
            font=("Arial", 12),
            command=lambda: self.stop_alarm(dialog)
        ).pack(side="left", padx=10)
        
        # Snooze button
        tk.Button(
            buttons_frame,
            text=f"Snooze ({self.snooze_time} min)",
            font=("Arial", 12),
            command=lambda: self.snooze_alarm(dialog)
        ).pack(side="left", padx=10)
    
    def stop_alarm(self, dialog):
        """Stop the alarm sound and close the dialog."""
        pygame.mixer.music.stop()
        dialog.destroy()
        self.active_alarm = None
    
    def snooze_alarm(self, dialog):
        """Snooze the alarm for a few minutes."""
        pygame.mixer.music.stop()
        dialog.destroy()
        
        # Create a new temporary alarm for the snooze time
        now = datetime.datetime.now()
        snooze_time = now + datetime.timedelta(minutes=self.snooze_time)
        
        snooze_alarm = dict(self.active_alarm)  # Copy the active alarm
        snooze_alarm["hour"] = snooze_time.hour
        snooze_alarm["minute"] = snooze_time.minute
        snooze_alarm["days"] = []  # Don't repeat
        snooze_alarm["name"] = f"{self.active_alarm['name']} (Snoozed)"
        
        # Add the snoozed alarm
        self.alarms.append(snooze_alarm)
        self.refresh_alarm_list()
        self.save_alarms()
        
        self.active_alarm = None
        self.status_var.set(f"Alarm snoozed for {self.snooze_time} minutes")

if __name__ == "__main__":
    root = tk.Tk()
    app = AlarmClock(root)
    root.mainloop()

Requirements:

Install the required packages:

pip install pygame

You'll also need an alarm sound file (MP3 or WAV) named "alarm.mp3" in the same directory as the script, or specify a different file path.

How It Works:

  1. The application displays a digital clock with the current time and date
  2. Users can create alarms by specifying time, name, sound, and recurring days
  3. A background thread continuously checks if any alarms should be triggered
  4. When an alarm is triggered, a dialog appears and the sound plays
  5. Users can dismiss the alarm or snooze it
  6. Alarms are saved to a JSON file for persistence

Extensions and Improvements:

  • Add a custom snooze time option
  • Implement different alarm types (countdown, timer, stopwatch)
  • Add a feature to gradually increase alarm volume
  • Create a system tray icon for background operation
  • Add themes and color schemes for the clock display
  • Implement alarm import/export functionality

4.4 QR Code Generator

This project creates a QR code generator application that converts text, URLs, contact information, and other data into QR codes with customizable settings.

Key Concepts Used:

  • Working with external libraries (qrcode)
  • Image generation and manipulation
  • GUI development with Tkinter
  • File handling for saving QR codes
  • Data formatting for different QR code types

Project Implementation:

QR Code Generator
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import qrcode
from PIL import Image, ImageTk
import os
import io

class QRCodeGenerator:
    def __init__(self, root):
        self.root = root
        self.root.title("QR Code Generator")
        self.root.geometry("800x600")
        self.root.configure(bg="#f5f5f5")
        
        # QR Code parameters
        self.qr_version = 1  # QR code version (1-40)
        self.qr_box_size = 10  # Size of each box in the QR code
        self.qr_border = 4  # Border size
        self.qr_color = "black"  # Foreground color
        self.qr_bg_color = "white"  # Background color
        
        # Current QR image
        self.current_qr_image = None
        
        # Create main frame
        self.main_frame = ttk.Frame(root, padding="20")
        self.main_frame.pack(fill="both", expand=True)
        
        # Title
        self.title_label = ttk.Label(
            self.main_frame,
            text="QR Code Generator",
            font=("Arial", 18, "bold")
        )
        self.title_label.pack(pady=10)
        
        # Split into left and right frames
        self.content_frame = ttk.Frame(self.main_frame)
        self.content_frame.pack(fill="both", expand=True)
        
        # Left frame for input options
        self.left_frame = ttk.Frame(self.content_frame, padding=10)
        self.left_frame.pack(side="left", fill="both", expand=True)
        
        # Right frame for QR code display
        self.right_frame = ttk.Frame(self.content_frame, padding=10)
        self.right_frame.pack(side="right", fill="both", expand=True)
        
        # QR Code type selection
        self.type_frame = ttk.LabelFrame(self.left_frame, text="QR Code Type", padding=10)
        self.type_frame.pack(fill="x", pady=5)
        
        self.qr_type_var = tk.StringVar(value="URL")
        qr_types = ["URL", "Text", "Email", "Phone", "SMS", "WiFi", "vCard"]
        
        for i, qr_type in enumerate(qr_types):
            ttk.Radiobutton(
                self.type_frame,
                text=qr_type,
                variable=self.qr_type_var,
                value=qr_type,
                command=self.update_input_fields
            ).grid(row=i//3, column=i%3, sticky="w", padx=10, pady=5)
        
        # Input fields frame
        self.input_frame = ttk.LabelFrame(self.left_frame, text="QR Code Content", padding=10)
        self.input_frame.pack(fill="both", expand=True, pady=5)
        
        # Initial fields (will be updated based on QR type)
        self.input_fields = {}
        
        # Settings frame
        self.settings_frame = ttk.LabelFrame(self.left_frame, text="QR Code Settings", padding=10)
        self.settings_frame.pack(fill="x", pady=5)
        
        # Version setting
        ttk.Label(self.settings_frame, text="Version:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        self.version_var = tk.IntVar(value=self.qr_version)
        version_spinner = ttk.Spinbox(
            self.settings_frame,
            from_=1,
            to=40,
            textvariable=self.version_var,
            width=5,
            command=self.update_settings
        )
        version_spinner.grid(row=0, column=1, sticky="w", padx=5, pady=5)
        
        # Box size setting
        ttk.Label(self.settings_frame, text="Box Size:").grid(row=0, column=2, sticky="w", padx=5, pady=5)
        self.box_size_var = tk.IntVar(value=self.qr_box_size)
        box_size_spinner = ttk.Spinbox(
            self.settings_frame,
            from_=1,
            to=50,
            textvariable=self.box_size_var,
            width=5,
            command=self.update_settings
        )
        box_size_spinner.grid(row=0, column=3, sticky="w", padx=5, pady=5)
        
        # Border setting
        ttk.Label(self.settings_frame, text="Border:").grid(row=1, column=0, sticky="w", padx=5, pady=5)
        self.border_var = tk.IntVar(value=self.qr_border)
        border_spinner = ttk.Spinbox(
            self.settings_frame,
            from_=0,
            to=10,
            textvariable=self.border_var,
            width=5,
            command=self.update_settings
        )
        border_spinner.grid(row=1, column=1, sticky="w", padx=5, pady=5)
        
        # Color settings
        ttk.Label(self.settings_frame, text="Colors:").grid(row=1, column=2, sticky="w", padx=5, pady=5)
        self.color_button = ttk.Button(
            self.settings_frame,
            text="Choose Colors",
            command=self.choose_colors
        )
        self.color_button.grid(row=1, column=3, sticky="w", padx=5, pady=5)
        
        # Generate button
        self.generate_button = ttk.Button(
            self.left_frame,
            text="Generate QR Code",
            command=self.generate_qr_code
        )
        self.generate_button.pack(fill="x", pady=10)
        
        # QR code display
        self.qr_frame = ttk.LabelFrame(self.right_frame, text="Generated QR Code", padding=10)
        self.qr_frame.pack(fill="both", expand=True)
        
        self.qr_display = ttk.Label(self.qr_frame)
        self.qr_display.pack(fill="both", expand=True, padx=10, pady=10)
        
        # Save button
        self.save_button = ttk.Button(
            self.right_frame,
            text="Save QR Code",
            command=self.save_qr_code,
            state="disabled"
        )
        self.save_button.pack(fill="x", pady=10)
        
        # Status bar
        self.status_var = tk.StringVar(value="Ready")
        self.status_bar = ttk.Label(
            root,
            textvariable=self.status_var,
            relief=tk.SUNKEN,
            anchor=tk.W,
            padding=(5, 2)
        )
        self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        
        # Initialize input fields
        self.update_input_fields()
    
    def update_input_fields(self):
        """Update input fields based on selected QR code type."""
        # Clear existing fields
        for widget in self.input_frame.winfo_children():
            widget.destroy()
        
        self.input_fields = {}
        qr_type = self.qr_type_var.get()
        
        if qr_type == "URL":
            ttk.Label(self.input_frame, text="URL:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
            self.input_fields["url"] = ttk.Entry(self.input_frame, width=40)
            self.input_fields["url"].grid(row=0, column=1, sticky="ew", padx=5, pady=5)
            self.input_fields["url"].insert(0, "https://")
        
        elif qr_type == "Text":
            ttk.Label(self.input_frame, text="Text:").grid(row=0, column=0, sticky="nw", padx=5, pady=5)
            self.input_fields["text"] = tk.Text(self.input_frame, width=40, height=5)
            self.input_fields["text"].grid(row=0, column=1, sticky="ew", padx=5, pady=5)
        
        elif qr_type == "Email":
            ttk.Label(self.input_frame, text="To:").grid(row=

Post a Comment

0 Comments