{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "a1a5ad2e",
   "metadata": {},
   "source": [
    "# API Fundamentals: GET, Params, and Error Handling"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b92032bf",
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests\n",
    "import json"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ef8ddb3",
   "metadata": {},
   "source": [
    "## What Is an API?\n",
    "\n",
    "An **API** (Application Programming Interface) is a defined way for two programs to communicate. A **REST API** (Representational State Transfer) is the most common style for web APIs:\n",
    "\n",
    "- The client (your code) sends an **HTTP request** to a URL called an **endpoint**\n",
    "- The server processes the request and sends back an **HTTP response** — usually JSON\n",
    "\n",
    "Think of it like a restaurant menu: the menu lists what you can order (endpoints), you place an order (request), and the kitchen sends back your food (response).\n",
    "\n",
    "### HTTP Methods\n",
    "\n",
    "| Method | Typical use |\n",
    "|---|---|\n",
    "| `GET` | Retrieve data (read-only, safe to repeat) |\n",
    "| `POST` | Create a new resource |\n",
    "| `PUT` / `PATCH` | Update an existing resource |\n",
    "| `DELETE` | Remove a resource |\n",
    "\n",
    "### HTTP Status Codes\n",
    "\n",
    "| Code | Meaning |\n",
    "|---|---|\n",
    "| `200 OK` | Request succeeded |\n",
    "| `201 Created` | Resource was created |\n",
    "| `400 Bad Request` | Client sent invalid data |\n",
    "| `401 Unauthorized` | Authentication required |\n",
    "| `404 Not Found` | Endpoint or resource doesn't exist |\n",
    "| `500 Internal Server Error` | Something went wrong on the server |"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79706fbd",
   "metadata": {},
   "source": [
    "## Making GET Requests with `requests`\n",
    "\n",
    "The `requests` library is the standard Python tool for HTTP. Install it with `pip install requests`.\n",
    "\n",
    "```python\n",
    "response = requests.get(url)\n",
    "response.status_code   # integer status code (200, 404, …)\n",
    "response.headers       # dict of response headers\n",
    "response.text          # response body as a string\n",
    "response.json()        # response body parsed as JSON → dict or list\n",
    "```\n",
    "\n",
    "We'll use **httpbin.org** — a free service that echoes back your request, perfect for learning."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6227929c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# httpbin.org/get echoes back details about your GET request as JSON\n",
    "response = requests.get('https://httpbin.org/get')\n",
    "\n",
    "print(\"Status code:\", response.status_code)          # 200\n",
    "print(\"Content-Type:\", response.headers['Content-Type'])\n",
    "\n",
    "# Parse the JSON body into a Python dict\n",
    "data = response.json()\n",
    "print(\"\\nYour IP address:\", data['origin'])\n",
    "print(\"Request URL:\", data['url'])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "01bd9d44",
   "metadata": {},
   "source": [
    "## Query Parameters\n",
    "\n",
    "Most APIs accept configuration via **query parameters** — key/value pairs appended to the URL after `?`. Instead of building the URL string manually, pass a `params` dict to `requests.get()`:\n",
    "\n",
    "```python\n",
    "# Manual:   https://example.com/search?q=python&page=2\n",
    "# With params dict (same result, cleaner):\n",
    "requests.get('https://example.com/search', params={'q': 'python', 'page': 2})\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "38aeaab0",
   "metadata": {},
   "outputs": [],
   "source": [
    "# httpbin.org/get echoes back the params you send\n",
    "params = {'course': 'IST5551', 'chapter': 15}\n",
    "response = requests.get('https://httpbin.org/get', params=params)\n",
    "\n",
    "data = response.json()\n",
    "print(\"Full URL requested:\", data['url'])\n",
    "# → https://httpbin.org/get?course=IST5551&chapter=15\n",
    "\n",
    "print(\"Args received by server:\")\n",
    "for key, value in data['args'].items():\n",
    "    print(f\"  {key} = {value}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6e917610",
   "metadata": {},
   "source": [
    "## Error Handling\n",
    "\n",
    "Two levels of errors can occur:\n",
    "\n",
    "1. **Network error** — no connection, timeout, DNS failure → `requests` raises an exception\n",
    "2. **HTTP error** — server responded but with a 4xx or 5xx status → `response.raise_for_status()` raises `HTTPError`\n",
    "\n",
    "```python\n",
    "try:\n",
    "    response = requests.get(url, timeout=5)\n",
    "    response.raise_for_status()          # raises HTTPError for 4xx/5xx\n",
    "    data = response.json()\n",
    "except requests.exceptions.Timeout:\n",
    "    print(\"Request timed out\")\n",
    "except requests.exceptions.HTTPError as e:\n",
    "    print(f\"HTTP error {e.response.status_code}: {e}\")\n",
    "except requests.exceptions.RequestException as e:\n",
    "    print(f\"Network error: {e}\")\n",
    "```\n",
    "\n",
    "Always set a **timeout** — without one, your code can hang indefinitely."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bfd743a1",
   "metadata": {},
   "outputs": [],
   "source": [
    "def safe_get(url, params=None):\n",
    "    \"\"\"Make a GET request and return the JSON data, or None on failure.\"\"\"\n",
    "    try:\n",
    "        response = requests.get(url, params=params, timeout=5)\n",
    "        response.raise_for_status()\n",
    "        return response.json()\n",
    "    except requests.exceptions.Timeout:\n",
    "        print(f\"Timeout requesting {url}\")\n",
    "    except requests.exceptions.HTTPError as e:\n",
    "        print(f\"HTTP {e.response.status_code} error from {url}\")\n",
    "    except requests.exceptions.RequestException as e:\n",
    "        print(f\"Request failed: {e}\")\n",
    "    return None\n",
    "\n",
    "\n",
    "# Test with a valid URL\n",
    "data = safe_get('https://httpbin.org/get', params={'test': 'ok'})\n",
    "if data:\n",
    "    print(\"Success — status via raise_for_status passed\")\n",
    "    print(\"Args:\", data['args'])\n",
    "\n",
    "# Test with an invalid URL (404)\n",
    "bad = safe_get('https://httpbin.org/status/404')\n",
    "print(\"404 result:\", bad)   # None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1bbed5db",
   "metadata": {},
   "outputs": [],
   "source": [
    "### Exercise: Weather API\n",
    "#   The Open-Meteo API (https://api.open-meteo.com) is free — no key required.\n",
    "#   Endpoint: https://api.open-meteo.com/v1/forecast\n",
    "#   Required query params:\n",
    "#     latitude=<float>    longitude=<float>    current=temperature_2m,wind_speed_10m\n",
    "#\n",
    "#   1. Use safe_get() to fetch current weather for New York (lat=40.71, lon=-74.01).\n",
    "#   2. Extract and print the current temperature (°C) and wind speed (km/h).\n",
    "#      The values are inside data['current'].\n",
    "#   3. Fetch weather for a second city of your choice and compare temperatures.\n",
    "### Your code starts here.\n",
    "BASE_URL = 'https://api.open-meteo.com/v1/forecast'\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1ea7d2b0",
   "metadata": {},
   "outputs": [],
   "source": [
    "### Solution\n",
    "BASE_URL = 'https://api.open-meteo.com/v1/forecast'\n",
    "\n",
    "cities = {\n",
    "    'New York':    {'latitude': 40.71, 'longitude': -74.01},\n",
    "    'Los Angeles': {'latitude': 34.05, 'longitude': -118.24},\n",
    "}\n",
    "\n",
    "for city, coords in cities.items():\n",
    "    params = {**coords, 'current': 'temperature_2m,wind_speed_10m'}\n",
    "    data = safe_get(BASE_URL, params=params)\n",
    "    if data:\n",
    "        current = data['current']\n",
    "        temp  = current['temperature_2m']\n",
    "        wind  = current['wind_speed_10m']\n",
    "        print(f\"{city}: {temp}°C, wind {wind} km/h\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e19a61f",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "| Concept | Key idea |\n",
    "|---|---|\n",
    "| REST API | URL endpoints that accept HTTP requests and return data (usually JSON) |\n",
    "| `requests.get(url)` | Make a GET request; returns a `Response` object |\n",
    "| `response.status_code` | Integer HTTP status (200 = OK, 404 = Not Found, …) |\n",
    "| `response.json()` | Parse the JSON body into a Python dict or list |\n",
    "| `params={}` | Pass query parameters cleanly — `requests` builds the URL string |\n",
    "| `response.raise_for_status()` | Raises `HTTPError` on 4xx/5xx responses |\n",
    "| `timeout=5` | Always set a timeout to avoid hanging indefinitely |\n",
    "| `requests.exceptions.RequestException` | Base class for all `requests` errors |"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
