Python Programming Guide
6. LOGGING
5 LEVELS (lowest → highest)
• DEBUG → developer tracing details
• INFO → normal positive progress
• WARNING → unexpected, still recovers
• ERROR → task failed
• CRITICAL → system breaking down
Key Rule: Setting a minimum level shows that level AND everything above it.
FORMAT PLACEHOLDERS
%(asctime)s → date & time of the log (e.g., 2024-01-15 10:23:45)
%(levelname)s → level name (INFO / WARNING / ERROR)
%(message)s → your log message
%(name)s → logger name
HANDLERS
StreamHandler() → screen (console) for development/debugging
FileHandler('[Link]') → saves to file for production/long runs
Both together → screen AND file (best practice)
BASIC SETUP PATTERN
import logging
[Link](
level=[Link],
format="%(asctime)s - %(levelname)s - %(message)s",
filename="[Link]"
)
[Link]("Program started")
[Link]("Something unexpected")
[Link]("Something failed")
7. PYTEST
What is it? Unit Testing = small programs that automatically check your functions work correctly.
Pytest = the library that discovers, runs, and reports those tests.
NAMING RULES
Test files → must start with test_ (e.g., test_calculator.py)
Test functions → must start with test_ (e.g., def test_add())
Import name → matches filename without .py
THE assert KEYWORD
assert condition → passes silently if True, raises AssertionError if False
assert result == 5
assert 'hi' in 'hello' # FAILS — 'hi' not in 'hello'
assert len([1,2,3]) == 3 # PASSES
BASIC TEST PATTERN
from calculator import add
def test_add():
result = add(2, 3) # ACT
assert result == 5 # ASSERT
AAA PATTERN (Every Professional Test)
def test_add():
# ARRANGE — set up inputs
a, b = 10, 5
# ACT — call the function
result = add(a, b)
# ASSERT — verify result
assert result == 15
CLI COMMANDS
pytest → run all tests in folder
pytest test_file.py → run specific file
pytest -v → verbose (see each test name)
pytest -k 'add' → run only tests with 'add' in name
8. APIs & THE requests LIBRARY
What is an API? API = messenger between your Python code and a server (get or send data).
Analogy: You (Python) → Waiter (API) → Kitchen (Server) → Response (Food)
HTTP METHODS
GET → fetch / read data ([Link](url))
POST → send / create data ([Link](url, json=data))
PUT → update existing data ([Link](url, json=data))
DELETE → delete data ([Link](url))
RESPONSE OBJECT
response.status_code → integer: 200, 201, 404...
[Link] → raw string response
[Link]() → Python dict or list
[Link] → response headers dict
[Link] → final URL that was called
response.raise_for_status() → raises HTTPError on 4xx/5xx
STATUS CODES
200 → Success (GET)
201 → Created (POST)
400 → Bad Request
401 → Unauthorized (bad/missing API key)
404 → Not Found
429 → Too Many Requests (rate limited)
500 → Server Error
BASIC GET REQUEST
response = [Link]('[Link]
if response.status_code == 200:
data = [Link]()
print(data['title'])
ERROR HANDLING TEMPLATE
def fetch_data(endpoint_id):
url = f'[Link]
try:
response = [Link](url, timeout=5)
response.raise_for_status()
return [Link]()
except [Link] as e:
print(f'HTTP Error: {e}')
except [Link]:
print('Request timed out.')
except [Link]:
print('No internet connection.')
return None
KEY RULES
• Always set timeout=5 (or higher for AI APIs — use 30)
• Always use raise_for_status() — catches all 4xx/5xx automatically
• params = WHAT you want (filters). headers = WHO you are (identity/auth).
• json= for APIs (auto-converts). data= for HTML forms only.
• Always return None on failure, always check 'if result is None' before use
• .get('key', default) for optional fields — never assume a key exists
• Use continue in loops to skip failed fetches instead of crashing