Go beyond single API calls: parse JSON responses into Python data structures, navigate nested and optional fields safely, paginate through multi-page GitHub API results using the Link header, and implement explicit exponential backoff for rate-limited endpoints. The lesson closes with a reusable json_api_client.py that imports check_credentials() from lesson 402 and composes all these patterns into a single, production-ready function.
Call the GitHub /repos endpoint and use response.json() to convert the response body into a Python dict. Access top-level string, integer, and boolean fields. Print formatted output from the parsed data.
mkdir -p ~/devops-python/lesson-403cd ~/devops-python/lesson-403source ~/devops-python/lesson-101/devops-env/bin/activatecp ~/devops-python/lesson-402/.env ~/devops-python/lesson-403/.envcp ~/devops-python/lesson-402/config.yaml ~/devops-python/lesson-403/config.yamlcp ~/devops-python/lesson-402/.gitignore ~/devops-python/lesson-403/.gitignoreimport os
import sys
from pathlib import Path
import requests
from dotenv import load_dotenv
sys.path.insert(0, str(Path.home() / 'devops-python' / 'lesson-304'))
from layered_config import load_config
load_dotenv()
config, _ = load_config(
config_file=Path('config.yaml'),
env_file=Path('.env'),
)
base = config.api_base_url
timeout = int(config.api_timeout)
token = os.environ.get('GITHUB_TOKEN', '')
headers = {
'Authorization': f'token {token}',
'Accept': 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
}
print('=== Parsing a JSON API response: GET /repos/python/cpython ===')
r = requests.get(f'{base}/repos/python/cpython', headers=headers, timeout=timeout)
r.raise_for_status()
# response.json() parses the JSON body and returns a Python dict
repo = r.json()
print(f'Type of response.json(): {type(repo).__name__}')
print()
# Access top-level fields by key
print(f'full_name: {repo["full_name"]}')
print(f'description: {repo["description"]}')
print(f'language: {repo["language"]}')
print(f'default_branch: {repo["default_branch"]}')
print(f'stargazers_count: {repo["stargazers_count"]:,}')
print(f'open_issues_count: {repo["open_issues_count"]:,}')
print(f'forks_count: {repo["forks_count"]:,}')
print(f'private: {repo["private"]}')
print(f'archived: {repo["archived"]}')
print()
# Show the total key count so learners understand the full response is larger
print(f'Total top-level keys in response: {len(repo)}')
print('(Printing selected fields only ā use print(repo.keys()) to explore all)')
print()
print('Content-Type header:', r.headers.get('Content-Type', 'n/a'))
print('Status code:', r.status_code)python3 parse_response.pyThe requests.Response.json() method calls json.loads() on the response body and returns a Python object ā a dict for JSON objects, a list for JSON arrays, or a scalar for bare JSON values.
The GitHub /repos endpoint returns a JSON object (dict) with over 100 keys describing the repository.
You access fields with the same dict syntax you would use for any Python dict: repo['key'].
The Content-Type: application/json header confirms the response body is JSON before .json() is called ā requests will raise json.JSONDecodeError if the body is not valid JSON, so checking the status code first (via raise_for_status()) ensures you are not trying to parse an error HTML page.
Output shows Type of response.json(): dict, followed by the cpython repository's name, description, language (Python ā GitHub's primary-language detection weights file bytes; cpython's large Python stdlib now outweighs its C source), default branch (main), star count (formatted with commas), open issues count, forks count, private status (False), and archived status (False).
The total key count line shows a number greater than 80.