Learn Python Series (#34) - Working with APIs in 2026: What's Changed

in #programming2 days ago (edited)

Learn Python Series (#34) - Working with APIs in 2026: What's Changed

python-logo.png

Repository

What will I learn?

  • You will learn what has changed in the Python API ecosystem since 2018;
  • how httpx compares to requests and when to use each;
  • modern authentication patterns (API keys, OAuth2, Bearer tokens);
  • how to use environment variables for secure credential management;
  • new error handling patterns and best practices for 2026.

Requirements

  • A working modern computer running macOS, Windows or Ubuntu;
  • An installed Python 3(.11+) distribution, such as (for example) the Anaconda Distribution;
  • Familiarity with the basics from Learn Python Series #13-17 (recommended but not required);
  • The ambition to learn Python programming.

Difficulty

  • Intermediate

Curriculum (of the Learn Python Series):

Learn Python Series (#34) - Working with APIs in 2026: What's Changed

Welcome back to the Learn Python Series! It's been a while since episodes #13-17, where we built a web crawler using the requests library and fetched data from the CoinMarketCap API. If you haven't read those episodes, I encourage you to check them out - the fundamentals we covered there still apply today.

But it's now 2026, and the Python ecosystem has evolved. In this episode, we'll focus on what's new and what's changed - not rehashing the basics, but building on them.

Quick refresher: What we covered in #13-17

Back in 2018, we learned:

  • Installing and using the requests library (#13)
  • Making GET requests and parsing HTML with BeautifulSoup
  • Working with JSON responses (#15)
  • Fetching cryptocurrency data from APIs (#16, #17)

Those fundamentals haven't changed. What HAS changed is the tooling and best practices around them.

What's new: httpx - The modern alternative

While requests is still perfectly valid, httpx has emerged as a modern alternative that offers both sync AND async support:

pip install httpx
import httpx

# Synchronous (just like requests)
response = httpx.get("https://api.coingecko.com/api/v3/ping")
print(response.json())

# The API is nearly identical to requests
response = httpx.get(
    "https://api.example.com/data",
    params={"key": "value"},
    headers={"Accept": "application/json"},
    timeout=10.0
)

When to use which:

  • requests: Battle-tested, huge ecosystem, synchronous only
  • httpx: Modern, async support built-in, HTTP/2 support

Nota bene: If you're doing async programming (which we'll cover in episodes #39-40), httpx is the clear choice. For simple synchronous scripts, either works fine.

Modern authentication patterns

In 2018, we mostly dealt with simple API keys in URLs. Today's APIs often use more sophisticated auth:

Bearer tokens (JWT)

import httpx

token = "your_jwt_token_here"

response = httpx.get(
    "https://api.example.com/protected",
    headers={"Authorization": f"Bearer {token}"}
)

OAuth2 with httpx

import httpx

# OAuth2 client credentials flow
token_response = httpx.post(
    "https://auth.example.com/oauth/token",
    data={
        "grant_type": "client_credentials",
        "client_id": "your_client_id",
        "client_secret": "your_client_secret"
    }
)
access_token = token_response.json()["access_token"]

# Use the token
client = httpx.Client(
    headers={"Authorization": f"Bearer {access_token}"}
)
response = client.get("https://api.example.com/data")

Secure credential management

Never hardcode credentials. This was true in 2018, but I didn't emphasize it enough. Here's the proper way:

import os
from dotenv import load_dotenv

# Load from .env file
load_dotenv()

API_KEY = os.getenv("API_KEY")
API_SECRET = os.getenv("API_SECRET")

if not API_KEY:
    raise ValueError("API_KEY environment variable not set")

Your .env file (never commit this!):

API_KEY=your_actual_key_here
API_SECRET=your_actual_secret_here

Your .gitignore:

.env

Improved error handling patterns

In #13-17, our error handling was basic. Here's the modern approach:

import httpx
from httpx import HTTPStatusError, RequestError, TimeoutException

def fetch_with_resilience(url: str, max_retries: int = 3) -> dict:
    """Fetch data with proper error handling and retries."""
    
    for attempt in range(max_retries):
        try:
            with httpx.Client(timeout=10.0) as client:
                response = client.get(url)
                response.raise_for_status()
                return response.json()
        
        except TimeoutException:
            print(f"Timeout on attempt {attempt + 1}")
            if attempt == max_retries - 1:
                raise
        
        except HTTPStatusError as e:
            if e.response.status_code == 429:  # Rate limited
                retry_after = int(e.response.headers.get("Retry-After", 60))
                print(f"Rate limited. Waiting {retry_after}s...")
                import time
                time.sleep(retry_after)
            elif e.response.status_code >= 500:
                print(f"Server error {e.response.status_code}, retrying...")
            else:
                raise  # Client errors (4xx) shouldn't be retried
        
        except RequestError as e:
            print(f"Network error: {e}")
            if attempt == max_retries - 1:
                raise
    
    raise Exception("Max retries exceeded")

Type hints for API responses

In episode #36, we'll dive deep into type hints. But here's a preview of how they improve API code:

from dataclasses import dataclass
from typing import Optional
import httpx

@dataclass
class CryptoPrice:
    symbol: str
    price_usd: float
    change_24h: float
    market_cap: Optional[float] = None

def get_crypto_price(coin_id: str) -> CryptoPrice:
    """Fetch and return typed cryptocurrency data."""
    response = httpx.get(
        "https://api.coingecko.com/api/v3/simple/price",
        params={
            "ids": coin_id,
            "vs_currencies": "usd",
            "include_24hr_change": "true",
            "include_market_cap": "true"
        }
    )
    response.raise_for_status()
    data = response.json()[coin_id]
    
    return CryptoPrice(
        symbol=coin_id.upper(),
        price_usd=data["usd"],
        change_24h=data.get("usd_24h_change", 0),
        market_cap=data.get("usd_market_cap")
    )

# Usage - IDE now knows exactly what fields are available
price = get_crypto_price("bitcoin")
print(f"{price.symbol}: ${price.price_usd:,.2f} ({price.change_24h:+.2f}%)")

HTTP/2 support

One thing requests doesn't support but httpx does:

import httpx

# HTTP/2 is automatic when the server supports it
with httpx.Client(http2=True) as client:
    response = client.get("https://www.google.com")
    print(f"HTTP Version: {response.http_version}")  # HTTP/2

Comparing 2018 vs 2026 approach

2018 (from episode #15):

import requests
import json

response = requests.get("https://api.coinmarketcap.com/v1/ticker/bitcoin/")
data = response.json()
print(data[0]["price_usd"])

2026 (modern approach):

import httpx
import os
from dataclasses import dataclass

@dataclass
class BitcoinPrice:
    usd: float
    change_24h: float

def get_bitcoin_price() -> BitcoinPrice:
    with httpx.Client(timeout=10.0) as client:
        response = client.get(
            "https://api.coingecko.com/api/v3/simple/price",
            params={"ids": "bitcoin", "vs_currencies": "usd", "include_24hr_change": "true"}
        )
        response.raise_for_status()
        data = response.json()["bitcoin"]
        return BitcoinPrice(usd=data["usd"], change_24h=data["usd_24h_change"])

price = get_bitcoin_price()
print(f"${price.usd:,.2f} ({price.change_24h:+.2f}%)")

The core concept is the same, but the 2026 version has:

  • Type safety with dataclasses
  • Proper timeout handling
  • Context manager for resource cleanup
  • Modern API endpoint (CoinMarketCap changed their API significantly)

What we've learned

In this refresher episode, we covered:

  • httpx as a modern alternative to requests
  • Bearer tokens and OAuth2 authentication
  • Secure credential management with environment variables
  • Improved error handling with retries
  • Type hints for API responses
  • HTTP/2 support

In the next episode, we'll explore advanced API patterns including POST/PUT/DELETE requests, handling pagination, and rate limiting strategies.

Thank you!

Thank you for your time! It's great to be back writing the Learn Python Series after all these years. If you have any questions, feel free to ask in the comment section below!

Sort:  

Thanks for your contribution to the STEMsocial community. Feel free to join us on discord to get to know the rest of us!

Please consider delegating to the @stemsocial account (85% of the curation rewards are returned).

Consider setting @stemsocial as a beneficiary of this post's rewards if you would like to support the community and contribute to its mission of promoting science and education on Hive.