================================================================================ PRACTICAL PYTHON SCRIPTING - CHALLENGE 1 SOLUTION Chapter 2: Configuration & Environment Variables Challenge: Load and Display Configuration ================================================================================ PROBLEM: Create a script that: 1. Creates a .env file with database settings 2. Loads it with load_dotenv() 3. Displays all settings (except the password) SOLUTION: ================================================================================ from dotenv import load_dotenv import os from pathlib import Path # Step 1: Create a .env file (if it doesn't exist) env_file = Path(__file__).parent / ".env" if not env_file.exists(): print("šŸ“ Creating .env file...") env_content = """# Database Configuration DB_HOST=localhost DB_NAME=learning_blog DB_USER=philip DB_PASS=AsT@1sAd3mon DB_PORT=3306 DB_CHARSET=utf8mb4 # Application Settings DEBUG=false LOG_LEVEL=info """ env_file.write_text(env_content) print(f"āœ… Created .env file at {env_file}\n") else: print(f"āœ… .env file already exists at {env_file}\n") # Step 2: Load the .env file load_dotenv(env_file) print("šŸ“‚ Loading configuration from .env...\n") # Step 3: Display configuration (except password) print("=" * 60) print("CONFIGURATION") print("=" * 60) # Database settings print("\nšŸ—„ļø Database Settings:") print(f" Host: {os.environ.get('DB_HOST')}") print(f" Name: {os.environ.get('DB_NAME')}") print(f" User: {os.environ.get('DB_USER')}") print(f" Port: {os.environ.get('DB_PORT')}") print(f" Charset: {os.environ.get('DB_CHARSET')}") print(f" Password: {'*' * 10} (hidden for security)") # Application settings print("\nāš™ļø Application Settings:") print(f" Debug: {os.environ.get('DEBUG')}") print(f" Log Level: {os.environ.get('LOG_LEVEL')}") print("\n" + "=" * 60) print("āœ… Configuration loaded successfully!") print("=" * 60) ================================================================================ HOW IT WORKS: ================================================================================ Step 1: CREATE THE .ENV FILE if not env_file.exists(): env_file.write_text(env_content) - Check if .env exists using .exists() - If not, create it with .write_text() - This makes the script self-contained (creates its own config) Step 2: LOAD THE CONFIGURATION load_dotenv(env_file) - Specify the exact path to the .env file - Reads the file and loads all KEY=VALUE pairs - Now all variables are available in os.environ Step 3: DISPLAY SETTINGS os.environ.get('DB_HOST') - Use .get() to safely access variables - Display all settings except the password - Password is replaced with asterisks for security Why hide the password? - Good security practice - Shows that you're thinking about secrets - Prevents accidental exposure in logs/output ================================================================================ TESTING THE SOLUTION: ================================================================================ 1. Create a file called "config_loader.py" with the solution 2. Run it: python config_loader.py Output on first run: šŸ“ Creating .env file... āœ… Created .env file at /path/to/script/.env šŸ“‚ Loading configuration from .env... ============================================================ CONFIGURATION ============================================================ šŸ—„ļø Database Settings: Host: localhost Name: learning_blog User: philip Port: 3306 Charset: utf8mb4 Password: ********** (hidden for security) āš™ļø Application Settings: Debug: false Log Level: info ============================================================ āœ… Configuration loaded successfully! ============================================================ 3. Run it again (second run): āœ… .env file already exists at /path/to/script/.env (The file is only created once) 4. Check that the .env file exists: ls -la .env cat .env Should show: # Database Configuration DB_HOST=localhost ... ================================================================================ WHAT EACH PART DOES: ================================================================================ from dotenv import load_dotenv - Import the load_dotenv function - This function reads .env files import os - Import os module for accessing environment variables - os.environ is a dictionary of all environment variables from pathlib import Path - Import Path for file operations Path(__file__).parent - Get the directory containing this script - Used to find/create .env in the script's directory env_file.exists() - Check if the .env file exists - Returns True/False env_file.write_text(content) - Create the file and write text to it - Creates the file if it doesn't exist - Overwrites if it does exist (use with care!) load_dotenv(env_file) - Load variables from the specified .env file - After this, variables are available in os.environ os.environ.get('KEY') - Get an environment variable safely - Returns None if the variable doesn't exist - Doesn't crash like os.environ['KEY'] would ================================================================================ ALTERNATIVE APPROACHES: ================================================================================ Approach 1: Load .env without creating it from dotenv import load_dotenv import os load_dotenv() # Searches for .env in current directory and parents # Just load it, don't create it # .env must already exist Approach 2: Multiple environment-specific .env files from dotenv import load_dotenv import os # Load based on environment env = os.environ.get('APP_ENV', 'development') load_dotenv(f'.env.{env}') # Uses: .env.development, .env.production, etc. Approach 3: Display all variables from dotenv import load_dotenv import os load_dotenv() print("All loaded variables:") for key, value in os.environ.items(): if key.startswith('DB_') or key.startswith('APP_'): print(f" {key}: {value}") ================================================================================ SECURITY CONSIDERATIONS: ================================================================================ 1. HIDING THE PASSWORD print(f"Password: {'*' * 10}") Don't accidentally print the actual password! This is why we explicitly hide it. 2. FILE PERMISSIONS After running this script: chmod 600 .env Make sure only the owner can read the file. 3. .gitignore Make sure .env is in .gitignore: echo ".env" >> .gitignore Never commit real credentials to Git! 4. .env.example Create a template for other developers: DB_HOST=localhost DB_NAME=learning_blog DB_USER=your_username DB_PASS=your_password_here Commit this to Git (without real values). ================================================================================ REAL-WORLD USAGE: ================================================================================ In actual scripts, you'd do this once at startup: def load_configuration(): from dotenv import load_dotenv import os load_dotenv() # Load from .env config = { 'db_host': os.environ.get('DB_HOST', 'localhost'), 'db_name': os.environ.get('DB_NAME'), 'db_user': os.environ.get('DB_USER'), 'db_pass': os.environ.get('DB_PASS'), 'db_port': int(os.environ.get('DB_PORT', '3306')), } return config # At the start of your script: config = load_configuration() # Then use throughout your script: print(f"Connecting to {config['db_host']}...") ================================================================================ KEY TAKEAWAYS: ================================================================================ āœ… load_dotenv() loads .env files into os.environ āœ… os.environ.get() safely accesses variables āœ… Always hide passwords in output āœ… Create .env files outside web root āœ… Never commit .env to Git āœ… Create .env.example as a template āœ… Use chmod 600 on .env for security āœ… This pattern works on Windows, Mac, and Linux The pattern you've learned: 1. Create/load .env file 2. Use load_dotenv() to load it 3. Access with os.environ.get() 4. Display safely (hide secrets) This is how ALL production Python scripts handle configuration! ================================================================================