Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
393: Securely Storing Secrets and Credentials
Look, I'll be honest with you: early in my career, I accidentally committed a production AWS secret key to a public GitHub repository. I realized it about ten minutes later, but in the world of security, ten minutes is an eternity. Bots scan GitHub commits in milliseconds. I spent the next three hours rotating every single credential in that account. It was a stressful afternoon that taught me a lesson I'll never forget: never, ever put secrets in your source code.
Today, we're going to build a simple weather notification script that calls an external API. It’s a small example, but it represents the exact pattern you'll use for database passwords, API tokens, and SSH keys in professional projects.
The temptation to just hardcode it
When you're just trying to get something to work, it's tempting to just paste the key right into the string. I'm going to show you exactly how I started this project—and why it's a mistake.
import requests
# I'm just testing this locally, so it's fine, right? (Wrong.)
API_KEY = "sk_live_51MzW2eLp9kXzR4jQ7nBv8mPq"
CITY = "Seattle"
def get_weather():
url = f"https://api.openweathermap.org/data/2.5/weather?q={CITY}&appid={API_KEY}"
response = requests.get(url)
return response.json()
print(get_weather())
It works. It's fast. But the moment you git commit this, that key is etched into your version history forever. Even if you delete the line in a later commit, the key is still there in the git history. Now we have to fix it.
Moving secrets to a .env file
The standard way to handle this in Python is using environment variables. For local development, we use a .env file. This is a simple text file that lives in your project root but is ignored by your version control system.
First, I'll install the python-dotenv package, which lets Python read these files easily:
pip install python-dotenv
Then, I'll create a file named .env in the same folder as my script:
# .env file
WEATHER_API_KEY=sk_live_51MzW2eLp9kXzR4jQ7nBv8mPq
CITY=Seattle
Now, here is the most important step. I need to tell Git to ignore this file. I'll create a .gitignore file and add one line to it:
.env
Connecting the dots in Python
Now we can rewrite the script to pull these values from the environment. I prefer using os.getenv() over os.environ[] because getenv returns None if the key is missing, rather than crashing your whole program with a KeyError.
import os
import requests
from dotenv import load_dotenv
# This looks for a .env file and loads the variables into the environment
load_dotenv()
# Now we pull them from the system environment
API_KEY = os.getenv("WEATHER_API_KEY")
CITY = os.getenv("CITY", "New York") # I added a default value just in case
def get_weather():
if not API_KEY:
raise ValueError("API_KEY not found! Did you forget the .env file?")
url = f"https://api.openweathermap.org/data/2.5/weather?q={CITY}&appid={API_KEY}"
response = requests.get(url)
return response.json()
print(get_weather())
Why this approach actually works
By doing this, we've decoupled our configuration (the secrets) from our logic (the code). When you eventually deploy this script to a server or a cloud provider like Heroku or AWS, you won't upload a .env file. Instead, those platforms have a dedicated "Environment Variables" or "Secrets" section in their dashboard where you paste the keys. Your code remains exactly the same; it just asks the operating system for the value of WEATHER_API_KEY, and the OS provides it.
📋 Practical Task
Exercise: Migrating a Hardcoded Database Password to a Secure .env Configuration
You have been handed a legacy script that connects to a database, but the developer left the credentials in plain text. Your task is to secure this script.
The legacy code (database_sync.py):
import psycopg2
def sync_data():
# SECURITY RISK: Hardcoded credentials
conn = psycopg2.connect(
dbname="prod_db",
user="admin_user",
password="SuperSecretPassword123!",
host="db.example.com"
)
print("Connection successful!")
conn.close()
sync_data()
Your requirements:
- Create a
.envfile containing theDB_PASSWORD,DB_USER, andDB_HOST. - Create a
.gitignorefile to ensure the.envfile is not tracked. - Modify
database_sync.pyto usepython-dotenvandos.getenvto retrieve these credentials. - Add a check to ensure the program exits with a helpful error message if
DB_PASSWORDis missing from the environment.
There are no comments for now.