Python Advance 📂 Important concepts · 2 of 6 333 min read

Working with APIs in Python

Master API programming in Python with the requests library. Learn HTTP methods, query params vs JSON bodies, parsing JSON and XML responses, four authentication patterns, status code handling, retries with exponential backoff, connection-pooled sessions, and pagination. Includes 4 visual diagrams (request/response cycle, XML tree, Bearer auth flow, status code decision tree) and a complete production-shaped GitHub client with dataclass, retries, and typed error handling.

🌐
SectionSection第 01 节

The Story That Explains APIs 🍽️APIs समझाने वाली कहानी 🍽️一个讲清 API 的故事 🍽️

Ordering Food Without Entering the KitchenKitchen में गए बिना खाना मँगाना不进厨房也能点餐
You walk into a restaurant. You do not march into the kitchen, chop vegetables and cook. You read a menu, tell the waiter what you want, and a plate arrives. You do not know how the chef stores food or which stove they use. The menu is the contract: these dishes exist, this is how you ask, this is what you get back.

An API (Application Programming Interface) works the same way. A server somewhere has data, or can do some work. It publishes a "menu" of URLs you can call. You send a request in a standard format. The server does the kitchen work and sends back a plate — usually a JSON document, sometimes XML.

Your Python program is the customer. requests is the waiter. The API is the menu. That is the whole idea.
आप एक restaurant में जाते हैं। आप kitchen में घुसकर सब्ज़ियाँ नहीं काटते और खाना नहीं पकाते। आप menu पढ़ते हैं, waiter को बताते हैं, और plate आ जाती है। आपको नहीं पता कि chef सामान कहाँ रखता है या कौन-सा चूल्हा इस्तेमाल करता है। Menu ही समझौता है: ये dishes मौजूद हैं, ऐसे माँगिए, यह मिलेगा।

API (Application Programming Interface) भी ऐसे ही काम करता है। कहीं एक server के पास data है, या वह कोई काम कर सकता है। वह URLs का एक "menu" प्रकाशित करता है जिन्हें आप call कर सकते हैं। आप तय format में request भेजते हैं। Server kitchen का काम करता है और plate वापस भेजता है — आम तौर पर एक JSON document, कभी-कभी XML।

आपका Python program ग्राहक है। requests waiter है। API menu है। बस इतनी-सी बात है।
你走进一家餐厅。你不会冲进厨房切菜做饭。你看 菜单,告诉服务员想吃什么,菜就端上来了。你不知道厨师怎么存放食材、用哪个灶台。菜单就是约定:有这些菜,这样点,会拿到这些。

API(应用程序编程接口)也是这样。某处的服务器有数据,或者能完成某项工作。它公布一份可以调用的 URL"菜单"。你按标准格式发送请求。服务器在"厨房"里干活,然后把"菜"送回来 — 通常是一个 JSON 文档,有时是 XML。

你的 Python 程序是顾客,requests 是服务员,API 是菜单。整个思路就是这样。

Every modern app talks to APIs — weather, payments, maps, AI models, stock prices, social feeds. In Python the standard tool is the requests library: one line to send an HTTP call, one line to parse the JSON. This tutorial goes from "what is an API" to "I can build a reliable client for a real service".

हर आधुनिक app APIs से बात करता है — मौसम, payments, maps, AI models, शेयर के भाव, social feeds। Python में मानक औज़ार requests library है: HTTP call भेजने के लिए एक line, JSON parse करने के लिए एक line। यह tutorial "API क्या है" से "मैं असली service के लिए भरोसेमंद client बना सकता हूँ" तक ले जाता है।

每个现代应用都在调用 API — 天气、支付、地图、AI 模型、股价、社交动态。在 Python 中,标准工具是 requests 库:一行代码发送 HTTP 请求,一行代码解析 JSON。本教程带你从"什么是 API"走到"我能为真实服务写出可靠的客户端"。

🧠
The Core Insightमूल बात核心要点

An API call is just a URL + a method + optional data. The server answers with a status code + optional data. Everything else — auth, pagination, retries, error handling — is polish around that one exchange.

API call बस URL + method + वैकल्पिक data है। Server status code + वैकल्पिक data से जवाब देता है। बाक़ी सब — auth, pagination, retries, error handling — उसी एक लेन-देन को निखारना है।

一次 API 调用就是 URL + 方法 + 可选数据。服务器回答 状态码 + 可选数据。其余一切 — 认证、分页、重试、错误处理 — 都是围绕这一次交换的打磨。

🧪
How the Examples on This Page Were Runइस Page के Examples कैसे चलाए गए本页示例是如何运行的

Every example was run for real with requests 2.34.2 on Python 3.13.16. The sandbox has no internet, so the hosts (api.github.com, httpbin.org, …) pointed to a local stand-in server with the same URLs and the same JSON shape. The code is unchanged — run it yourself and it talks to the real services. Live values such as star counts, names and headlines will differ, and they change every day. Seven simulators below are built from these real runs.

हर example requests 2.34.2 और Python 3.13.16 पर सच में चलाया गया। Sandbox में internet नहीं है, इसलिए hosts (api.github.com, httpbin.org, …) एक local stand-in server की ओर मोड़े गए, जिसके URLs और JSON का आकार वही है। Code नहीं बदला — आप ख़ुद चलाएँगे तो यह असली services से बात करेगा। Star counts, नाम और headlines जैसी live values अलग होंगी, और रोज़ बदलती हैं। नीचे के सात simulators इन्हीं असली runs से बने हैं।

每个示例都用 requests 2.34.2 在 Python 3.13.16 上真实运行过。沙箱没有互联网,所以这些主机(api.github.com、httpbin.org 等)被指向一台 本地替身服务器,它的 URL 和 JSON 结构 与真实服务相同。代码没有改动 — 你自己运行时就会访问真实服务。星标数、名字、标题等实时数据会不同,而且每天都在变。下面的七个模拟器都来自这些真实运行。


SectionSection第 02 节

Visual Diagram — The HTTP Request / Response Cycle 🔄चित्र — HTTP Request / Response चक्र 🔄图解 — HTTP 请求 / 响应循环 🔄

Figure 1 — One HTTP Round Trip, End to Endचित्र 1 — एक HTTP Round Trip, शुरू से अंत तक图 1 — 一次完整的 HTTP 往返
HTTP round tripYOUR PYTHONआपका PYTHON你的 PYTHONPROGRAMPROGRAM程序requests.get(...)INTERNETINTERNET互联网DNS · TCP · TLSAPI SERVERAPI SERVERAPI 服务器(remote)(दूर)(远程)api.example.comREQUESTREQUEST请求GET /weather?city=LondonAuthorization: Bearer <key>method + path + headers + optional bodymethod + path + headers + वैकल्पिक body方法 + 路径 + 请求头 + 可选请求体RESPONSERESPONSE响应200 OK · application/json{"city": "London", "temp": 14.2}status + headers + body (JSON / XML / bytes)status + headers + body (JSON / XML / bytes)状态码 + 响应头 + 响应体(JSON / XML / 字节)

One request goes out (green), one response comes back (orange).

एक request जाती है (हरी), एक response लौटता है (नारंगी)।

一个请求发出(绿色),一个响应返回(橙色)。

Every API call — however fancy — is one arrow going right and one arrow coming back. Everything else in this tutorial is a variation on that pattern.

हर API call — चाहे कितनी भी जटिल हो — एक तीर दाईं ओर जाता है और एक वापस आता है। इस tutorial में बाक़ी सब इसी pattern के रूप हैं।

每次 API 调用 — 无论多复杂 — 都是一支箭头向右发出,一支箭头返回。本教程的其余内容都是这个模式的变化。


SectionSection第 03 节

The Three Building Blocks of an API Call 🧱API Call की तीन ईंटें 🧱API 调用的三块基石 🧱

🔗
1 — Endpoint (URL)1 — Endpoint (URL)1 — 端点(URL)
what you ask aboutआप किसके बारे में पूछ रहे हैं你要问什么
A specific URL — called an endpoint — that names a resource.
https://api.github.com/users/torvalds
एक ख़ास URL — जिसे endpoint कहते हैं — जो किसी resource को नाम देता है।
https://api.github.com/users/torvalds
一个具体的 URL — 称为 端点 — 用来标识某个资源。
https://api.github.com/users/torvalds
🛠️
2 — Method (Verb)2 — Method (क्रिया)2 — 方法(动词)
what you want to doआप क्या करना चाहते हैं你想做什么
GET (read), POST (create), PUT/PATCH (update), DELETE (remove). The method tells the server your intent — only reading, or changing something.
GET (पढ़ना), POST (बनाना), PUT/PATCH (बदलना), DELETE (हटाना)। Method server को आपका इरादा बताता है — सिर्फ़ पढ़ना, या कुछ बदलना।
GET(读取)、POST(创建)、PUT/PATCH(更新)、DELETE(删除)。方法告诉服务器你的意图 — 只是读取,还是要修改。
📦
3 — Data (Payload)3 — Data (Payload)3 — 数据(载荷)
the detailsबारीकियाँ细节
Query strings (?city=London), a JSON body, form data, headers, cookies and auth tokens — the specifics of your request.
Query strings (?city=London), JSON body, form data, headers, cookies और auth tokens — आपकी request की बारीकियाँ।
查询字符串(?city=London)、JSON 请求体、表单数据、请求头、Cookie 和认证令牌 — 请求的具体内容。

Your First API Callआपकी पहली API Call你的第一次 API 调用

# pip install requests
import requests

# Public API - no auth required
r = requests.get("https://api.github.com/users/torvalds", timeout=5)

print(r.status_code)                 # 200 = success
print(r.headers["Content-Type"])     # what kind of data came back

data = r.json()                      # parse JSON -> Python dict
print(data["name"])
print(data["public_repos"])
print(data["bio"])                   # JSON null -> Python None
OUTPUT
200 application/json; charset=utf-8 Linus Torvalds 11 None

r.json() turned the JSON text into a normal Python dict. JSON null became Python None — that is why bio printed None.

r.json() ने JSON text को सामान्य Python dict में बदला। JSON का null Python का None बन गया — इसीलिए bio ने None print किया।

r.json() 把 JSON 文本变成了普通的 Python dict。JSON 的 null 变成了 Python 的 None — 所以 bio 打印出 None。

💡
Three Facts About requestsrequests के बारे में तीन बातें关于 requests 的三个事实

1. It is not in the standard library — run pip install requests. 2. Every call returns a Response object with .status_code, .headers, .text, .content and .json(). 3. By default nothing is retried and there is no timeout — a silent server can make your program wait for ever. Every real call needs timeout=.

1. यह standard library में नहीं है — pip install requests चलाइए। 2. हर call एक Response object लौटाती है जिसमें .status_code, .headers, .text, .content और .json() हैं। 3. Default रूप से कुछ भी retry नहीं होता और कोई timeout नहीं होता — चुप server आपके program को हमेशा इंतज़ार करा सकता है। हर असली call को timeout= चाहिए।

1. 它不在标准库中 — 需要运行 pip install requests。2. 每次调用都返回一个 Response 对象,带有 .status_code、.headers、.text、.content 和 .json()。3. 默认情况下不会重试,也 没有超时 — 一个不回应的服务器会让程序永远等下去。每个真实调用都需要 timeout=。

🔧
Fixed: Added timeout= and Live Values Changeसुधार: timeout= जोड़ा गया, और Live Values बदलती हैं已修正:补上了 timeout=,且实时数据会变化

The original first call had no timeout=, while its own tip says every serious call needs one — we added it. Also, the printed numbers (like public_repos) are live data: they were a snapshot when the original was written and are different today.

मूल पहली call में timeout= नहीं था, जबकि उसकी अपनी tip कहती है कि हर गंभीर call को इसकी ज़रूरत है — हमने जोड़ दिया। साथ ही, print हुए numbers (जैसे public_repos) live data हैं: वे मूल लिखते समय का snapshot थे और आज अलग हैं।

原文的第一个调用没有 timeout=,而它自己的提示却说每个正式调用都需要 — 我们补上了。另外,打印出的数字(如 public_repos)是实时数据:那只是原文写作时的快照,今天已经不同。


SectionSection第 04 节

The HTTP Methods — What They Mean, When to Use Each 🚦HTTP Methods — मतलब, और कब कौन-सा 🚦HTTP 方法 — 含义与使用时机 🚦

MethodMethod方法Purposeउद्देश्य用途Idempotent?Idempotent?幂等?requests callrequests callrequests 调用
GETRead data — no changes on the serverData पढ़ना — server पर कोई बदलाव नहीं读取数据 — 不改变服务器Yesहाँ是requests.get(url)
POSTCreate a new resource (or trigger an action)नया resource बनाना (या कोई action चलाना)创建新资源(或触发某个操作)Noनहीं否requests.post(url, json=...)
PUTReplace a resource entirelyResource को पूरा बदलना整体替换资源Yesहाँ是requests.put(url, json=...)
PATCHPartial update — change some fieldsआंशिक बदलाव — कुछ fields部分更新 — 只改部分字段Not guaranteedगारंटी नहीं不保证requests.patch(url, json=...)
DELETERemove a resourceResource हटाना删除资源Yesहाँ是requests.delete(url)
HEADLike GET, but the response has no body — check existence or headersGET जैसा, पर response में body नहीं — मौजूदगी या headers जाँचने के लिए与 GET 类似,但响应没有响应体 — 用来检查是否存在或查看响应头Yesहाँ是requests.head(url)

Idempotent means: sending the same request twice leaves the server in the same state as sending it once. It is about the server state, not the reply — a second DELETE may answer 404, but the item is still just gone.

Idempotent का मतलब: एक ही request दो बार भेजने पर server की हालत वैसी ही रहती है जैसी एक बार भेजने पर। यह server की हालत के बारे में है, जवाब के बारे में नहीं — दूसरा DELETE 404 दे सकता है, पर item बस हटा हुआ ही है।

幂等 的意思是:同一个请求发两次,服务器的状态与只发一次相同。它关注的是 服务器状态,而不是回复 — 第二次 DELETE 可能返回 404,但该条目依然只是被删除了。

🔧
Fixed: PATCH Is Not "Usually" Idempotentसुधार: PATCH "आम तौर पर" Idempotent नहीं है已修正:PATCH 并非"通常"幂等

The original table marked PATCH as "Usually" idempotent. The HTTP standard (RFC 5789) says PATCH is not idempotent in general. "Set title to X" is safe to repeat; "add one like" is not. The simulator below shows both.

मूल table में PATCH को "Usually" idempotent बताया गया था। HTTP मानक (RFC 5789) कहता है कि PATCH सामान्यतः idempotent नहीं है। "title को X कर दो" दोहराना सुरक्षित है; "एक like जोड़ो" नहीं। नीचे का simulator दोनों दिखाता है।

原表格把 PATCH 标为"通常"幂等。HTTP 标准(RFC 5789)规定 PATCH 一般来说 不是 幂等的。"把标题设为 X"可以安全重复;"点赞数加一"则不行。下面的模拟器两种都展示了。

import requests

BASE = "https://jsonplaceholder.typicode.com"   # public sandbox API

# GET - read one post
r = requests.get(f"{BASE}/posts/1", timeout=5)
print("GET   ", r.status_code, r.json()["title"][:30])

# POST - create a new post
new_post = {"title": "Hello", "body": "World", "userId": 1}
r = requests.post(f"{BASE}/posts", json=new_post, timeout=5)
print("POST  ", r.status_code, r.json())

# PUT - replace an existing post
r = requests.put(f"{BASE}/posts/1",
                 json={"id": 1, "title": "Updated", "body": "New", "userId": 1},
                 timeout=5)
print("PUT   ", r.status_code, r.json())

# PATCH - change only the title
r = requests.patch(f"{BASE}/posts/1", json={"title": "Just the title"}, timeout=5)
print("PATCH ", r.status_code, r.json()["title"], "| body kept:", r.json()["body"][:12] + "...")

# DELETE - remove
r = requests.delete(f"{BASE}/posts/1", timeout=5)
print("DELETE", r.status_code, r.json())
OUTPUT
GET 200 sunt aut facere repellat provi POST 201 {'title': 'Hello', 'body': 'World', 'userId': 1, 'id': 101} PUT 200 {'id': 1, 'title': 'Updated', 'body': 'New', 'userId': 1} PATCH 200 Just the title | body kept: quia et susc... DELETE 200 {}
🧪
A Sandbox That Pretendsदिखावा करने वाला Sandbox一个"假装"的沙箱

JSONPlaceholder is a fake API for practice: it answers like a real one (201 for POST, the new id 101), but it never really saves anything. That is perfect for learning — and it is why we use a stateful test API in the simulator below.

JSONPlaceholder अभ्यास के लिए एक नक़ली API है: यह असली की तरह जवाब देता है (POST पर 201, नया id 101), पर असल में कुछ भी save नहीं करता। सीखने के लिए यह बढ़िया है — और इसीलिए नीचे के simulator में हम ऐसा test API लेते हैं जो हालत याद रखता है।

JSONPlaceholder 是一个用于练习的假 API:它像真的一样回应(POST 返回 201,新 id 为 101),但从不真正保存任何东西。这非常适合学习 — 这也是为什么下面的模拟器使用了一个 会保存状态 的测试 API。

🔁 Simulator: Send It Twice🔁 Simulator: दो बार भेजिए🔁 模拟器:发送两次

Pick a request. It is sent twice to a small notes API that really keeps state. Compare the server state after call 1 and after call 2: if they match, the request is idempotent.

एक request चुनिए। इसे एक छोटे notes API को दो बार भेजा जाता है जो सच में हालत याद रखता है। Call 1 और call 2 के बाद server की हालत मिलाइए: अगर दोनों मेल खाएँ, तो request idempotent है।

选一个请求。它会被 发送两次 到一个真正保存状态的小型笔记 API。比较第 1 次和第 2 次调用后的服务器状态:如果相同,这个请求就是幂等的。

Send It Twiceदो बार भेजिए发送两次real runs · server keeps stateअसली runs · server हालत याद रखता है真实运行 · 服务器保存状态

SectionSection第 05 节

Query Params vs Request Body — When to Use Which 📮Query Params बनाम Request Body — कब कौन-सा 📮查询参数 vs 请求体 — 何时用哪个 📮

There are two places to send data with a request: the URL (query string) or the body. Mixing them up is a very common reason for "why won't my POST work?".

Request के साथ data भेजने की दो जगहें हैं: URL (query string) या body। इन्हें गड़बड़ाना "मेरा POST क्यों नहीं चल रहा?" का बहुत आम कारण है।

随请求发送数据有两个位置:URL(查询字符串)或 请求体。把两者搞混是"为什么我的 POST 不工作?"的一个非常常见的原因。

🔍 Query Parameters (URL)Query Parameters (URL)查询参数(URL)
Aspectपहलू方面Detailविवरण细节
Whereकहाँ位置After ? in the URLURL में ? के बादURL 中 ? 之后
Typical useआम उपयोग典型用途GET — filters, pagination, searchGET — filters, pagination, searchGET — 筛选、分页、搜索
Visible in logsLogs में दिखता日志中可见Yes — never put secrets hereहाँ — यहाँ secrets कभी न रखें是 — 千万别放机密
Exampleउदाहरण示例?city=Tokyo&units=metric
💼 Request Body (JSON)Request Body (JSON)请求体(JSON)
Aspectपहलू方面Detailविवरण细节
Whereकहाँ位置Inside the HTTP bodyHTTP body के अंदरHTTP 请求体内部
Typical useआम उपयोग典型用途POST / PUT / PATCH — new or changed recordsPOST / PUT / PATCH — नए या बदले recordsPOST / PUT / PATCH — 新建或修改记录
Visible in logsLogs में दिखता日志中可见Usually not — safer for structured dataआम तौर पर नहीं — structured data के लिए सुरक्षित通常不会 — 更适合结构化数据
Exampleउदाहरण示例{"name": "Ada", "age": 30}
import requests

# Query params - passed via params={}
r = requests.get(
    "https://api.open-meteo.com/v1/forecast",
    params={
        "latitude": 35.68,
        "longitude": 139.65,
        "current_weather": True,
    },
    timeout=5,
)
print(r.url)                          # the URL requests actually sent

# JSON body - passed via json={}
r = requests.post(
    "https://httpbin.org/post",
    json={"name": "Ada", "role": "engineer"},
    timeout=5,
)
print(r.json()["json"])               # httpbin echoes back what you sent
print(r.request.headers["Content-Type"])
OUTPUT
https://api.open-meteo.com/v1/forecast?latitude=35.68&longitude=139.65&current_weather=True {'name': 'Ada', 'role': 'engineer'} application/json

Look at the first line: params= built the whole query string for you, and Python's True was sent as the text True. With json=, requests turned the dict into JSON text and set Content-Type: application/json by itself.

पहली line देखिए: params= ने पूरी query string आपके लिए बनाई, और Python का True text True के रूप में गया। json= के साथ requests ने dict को JSON text में बदला और ख़ुद Content-Type: application/json लगाया।

看第一行:params= 替你拼好了整个查询字符串,Python 的 True 被当作文本 True 发送。使用 json= 时,requests 把字典转成 JSON 文本,并自动设置 Content-Type: application/json。

⚠️
Never Build URLs with String ConcatenationString जोड़कर URLs कभी न बनाएँ永远不要用字符串拼接 URL

Writing url + "?q=" + user_input breaks on spaces, &, +, # and non-English letters. Always pass params={...} and let requests encode everything. Same rule for JSON bodies: use json={...}, never hand-write the JSON string.

url + "?q=" + user_input लिखना spaces, &, +, # और ग़ैर-अंग्रेज़ी अक्षरों पर टूट जाता है। हमेशा params={...} दीजिए और requests को सब encode करने दीजिए। JSON bodies के लिए भी यही नियम: json={...} लीजिए, JSON string कभी हाथ से मत लिखिए।

写 url + "?q=" + user_input 遇到空格、&、+、# 和非英文字符就会出错。始终传入 params={...},让 requests 来编码一切。JSON 请求体也一样:用 json={...},永远不要手写 JSON 字符串。

import requests

user_input = "São Paulo & Rio"

# WRONG - glue the URL together by hand
r = requests.get("https://httpbin.org/get?q=" + user_input, timeout=5)
print("concat :", r.json()["args"])

# RIGHT - let requests encode it
r = requests.get("https://httpbin.org/get", params={"q": user_input}, timeout=5)
print("params :", r.json()["args"])
print("sent   :", r.url)
OUTPUT
concat : {'q': 'São Paulo ', ' Rio': ''} params : {'q': 'São Paulo & Rio'} sent : https://httpbin.org/get?q=S%C3%A3o+Paulo+%26+Rio

🔤 Simulator: URL Encoding Lab🔤 Simulator: URL Encoding Lab🔤 模拟器:URL 编码实验室

Pick a search term. Both versions were really sent; the boxes show the URL that went out and what the server actually received.

एक search term चुनिए। दोनों versions सच में भेजे गए; डिब्बे दिखाते हैं कि कौन-सा URL गया और server को असल में क्या मिला।

选一个搜索词。两个版本都真实发送过;方框里显示发出的 URL 以及服务器实际收到的内容。

URL Encoding LabURL Encoding LabURL 编码实验室6 inputs · real server echo6 inputs · असली server echo6 个输入 · 真实服务器回显
term =

SectionSection第 06 节

Parsing JSON — The Most Common Response Format 🧾JSON Parse करना — सबसे आम Response Format 🧾解析 JSON — 最常见的响应格式 🧾

Most modern web APIs speak JSON. requests parses it with one method call, and you get back normal Python objects: dict, list, str, int, float, bool and None.

ज़्यादातर आधुनिक web APIs JSON बोलते हैं। requests इसे एक method call से parse करता है, और आपको सामान्य Python objects मिलते हैं: dict, list, str, int, float, bool और None।

大多数现代 Web API 都使用 JSON。requests 一次方法调用就能解析它,你得到的是普通的 Python 对象:dict、list、str、int、float、bool 和 None。

JSONJSONJSONPython after r.json()r.json() के बाद Pythonr.json() 之后的 Python
{"a": 1} (object)dict
[1, 2] (array)list
"text"str
42 / 4.2int / float
true / falseTrue / False
nullNone
import requests

r = requests.get("https://api.github.com/repos/python/cpython", timeout=5)
data = r.json()                      # dict[str, Any]

print(data["full_name"])
print(data["stargazers_count"])
print(data["language"])
print(data["license"]["name"])       # nested access is just dict[key]

# Lists in JSON - same rules as normal Python
r = requests.get("https://api.github.com/repos/python/cpython/contributors",
                 params={"per_page": 3}, timeout=5)
for contributor in r.json():
    print(f"{contributor['login']:18s} {contributor['contributions']:>5} commits")
OUTPUT
python/cpython 70412 Python Other vstinner 9302 commits serhiy-storchaka 6110 commits gvanrossum 5321 commits
🔧
Fixed: The Original Numbers Were a Snapshotसुधार: मूल Numbers एक Snapshot थे已修正:原文的数字只是快照

The original showed fixed numbers, such as 62483 stars and "gvanrossum 1230 commits", as if they were the output. Live API data changes every day, and the contributor list is sorted by commit count, which also changes. Our output comes from the stand-in server — treat every number on this page as an example, not a fact about the repository.

मूल में 62483 stars और "gvanrossum 1230 commits" जैसे तय numbers ऐसे दिखाए गए जैसे वे output हों। Live API data रोज़ बदलता है, और contributor सूची commit संख्या से क्रमित होती है, जो भी बदलती है। हमारा output stand-in server से है — इस page के हर number को उदाहरण मानिए, repository के बारे में तथ्य नहीं।

原文把 62483 颗星、"gvanrossum 1230 commits"这样的固定数字当作输出展示。实时 API 数据每天都在变,贡献者列表按提交次数排序,这个次数也在变。我们的输出来自替身服务器 — 请把本页每个数字都当作示例,而不是关于该仓库的事实。

Safe Access to Optional KeysOptional Keys तक सुरक्षित पहुँच安全访问可选键

import requests

data = requests.get("https://api.github.com/users/torvalds", timeout=5).json()

# The key may be missing, or present with the value null (None)
print(data.get("bio"))                          # None (here: present, but null)
print(data.get("bio") or "no bio available")    # handles missing AND null
print(data.get("twitter", "no such key"))       # key missing -> default

# Nested optional access - "or {}" protects against a missing or null parent
license_name = (data.get("license") or {}).get("name", "unknown")
print(license_name)

print(data["twitter"])                          # KeyError: missing key
OUTPUT
None no bio available no such key unknown Traceback (most recent call last): File "demo.py", line 14, in <module> print(data["twitter"]) # KeyError: missing key ~~~~^^^^^^^^^^^ KeyError: 'twitter'

Two different problems look alike: a key that is missing (data["twitter"] → KeyError) and a key that is present with the value null (data["bio"] → None). data.get(key) or default handles both.

दो अलग समस्याएँ एक जैसी दिखती हैं: ग़ायब key (data["twitter"] → KeyError) और मौजूद key जिसकी value null है (data["bio"] → None)। data.get(key) or default दोनों संभालता है।

两个看起来相似的问题其实不同:键 缺失(data["twitter"] → KeyError),以及键存在但值为 null(data["bio"] → None)。data.get(key) or default 两种情况都能处理。

🔧
Fixed: There Is No Walrus in That Codeसुधार: उस Code में कोई Walrus नहीं है已修正:那段代码里没有海象运算符

The original comment said "walrus + get chains", but the line (data.get("license") or {}).get("name", "unknown") uses no walrus operator (:=). It uses or {} so that a missing or null parent becomes an empty dict.

मूल comment में "walrus + get chains" लिखा था, पर (data.get("license") or {}).get("name", "unknown") line में कोई walrus operator (:=) नहीं है। यह or {} लेती है ताकि ग़ायब या null parent ख़ाली dict बन जाए।

原注释写的是"walrus + get chains",但 (data.get("license") or {}).get("name", "unknown") 这一行根本没有海象运算符(:=)。它用的是 or {},让缺失 或 为 null 的父对象变成空字典。


SectionSection第 07 节

Parsing XML — For Legacy and Enterprise APIs 🌳XML Parse करना — पुराने और Enterprise APIs के लिए 🌳解析 XML — 用于旧系统和企业 API 🌳

XML still lives on in older or enterprise services — SOAP endpoints, government data feeds, RSS/Atom feeds, some payment gateways. Python's standard library reads it with xml.etree.ElementTree.

XML अब भी पुरानी या enterprise services में है — SOAP endpoints, सरकारी data feeds, RSS/Atom feeds, कुछ payment gateways। Python की standard library इसे xml.etree.ElementTree से पढ़ती है।

XML 仍存在于较老的或企业级服务中 — SOAP 端点、政府数据源、RSS/Atom 订阅、一些支付网关。Python 标准库用 xml.etree.ElementTree 读取它。

import requests
import xml.etree.ElementTree as ET

# Example: NASA's news RSS feed (XML)
r = requests.get("https://www.nasa.gov/feed/", timeout=5)
root = ET.fromstring(r.content)      # root is the outermost <rss> element
print(root.tag, root.attrib)

# RSS structure: rss > channel > item*
channel = root.find("channel")
for item in channel.findall("item")[:3]:
    title = item.findtext("title")
    link  = item.findtext("link")
    date  = item.findtext("pubDate")
    print(f"- {title}\n  {link}\n  {date}")
OUTPUT
rss {'version': '2.0'} - Sample story 1: Webb image of a spiral galaxy https://www.nasa.gov/image-article/webb-spiral/ Tue, 10 Nov 2026 14:00:00 +0000 - Sample story 2: Artemis crew training https://www.nasa.gov/news/artemis-training/ Mon, 09 Nov 2026 16:30:00 +0000 - Sample story 3: a strong solar flare https://www.nasa.gov/blogs/solar-flare/ Sun, 08 Nov 2026 09:15:00 +0000
Figure 2 — XML as a Treeचित्र 2 — XML एक पेड़ की तरह图 2 — XML 是一棵树
XML tree<rss><channel>root.find("channel")<item> #1<item> #2<item> #3channel.findall("item")<title> <link> <pubDate>item.findtext("title")

find() walks one branch; findall() lists all children with a tag.

find() एक शाखा पर चलता है; findall() किसी tag के सारे बच्चे देता है।

find() 沿一条分支走;findall() 列出某个标签的所有子节点。

🔧
Fixed: Wrong Feed Name, and a Dated Security Claimसुधार: ग़लत Feed नाम, और पुराना Security दावा已修正:订阅源名称有误,安全说法已过时

The original comment called https://www.nasa.gov/feed/ the "Astronomy Picture of the Day" feed — it is NASA's general news feed (APOD is a separate site). It also said the standard library must be replaced by defusedxml "to block XXE attacks". Python's docs say ElementTree does not load external entities, so it is safe from XXE, and with expat 2.4.1 or newer (this Python has 2.6.1) it is also protected against "billion laughs". defusedxml is still a good extra guard for hostile XML, but it is not required for XXE.

मूल comment ने https://www.nasa.gov/feed/ को "Astronomy Picture of the Day" feed कहा — यह NASA की सामान्य news feed है (APOD अलग site है)। यह भी कहा गया कि "XXE attacks रोकने के लिए" standard library की जगह defusedxml लेना ज़रूरी है। Python docs कहते हैं कि ElementTree external entities load नहीं करता, इसलिए XXE से सुरक्षित है, और expat 2.4.1 या नए (इस Python में 2.6.1) के साथ "billion laughs" से भी बचा है। शत्रुतापूर्ण XML के लिए defusedxml अब भी अच्छा अतिरिक्त बचाव है, पर XXE के लिए ज़रूरी नहीं।

原注释把 https://www.nasa.gov/feed/ 称为"每日天文图片"订阅源 — 其实它是 NASA 的综合新闻订阅源(APOD 是另一个网站)。原文还说必须用 defusedxml 替换标准库"以阻止 XXE 攻击"。Python 文档说明 ElementTree 不加载外部实体,因此不受 XXE 影响;在 expat 2.4.1 或更新版本(本 Python 为 2.6.1)下,它也能防御"billion laughs"。对于恶意 XML,defusedxml 仍是不错的额外防护,但并非防 XXE 所必需。


SectionSection第 08 节

Authentication — Proving Who You Are 🔑Authentication — आप कौन हैं यह साबित करना 🔑认证 — 证明你是谁 🔑

Most useful APIs need you to identify yourself. There are three common patterns. In every case, credentials belong in environment variables — never in code, never in git.

ज़्यादातर काम के APIs आपसे पहचान माँगते हैं। तीन आम patterns हैं। हर हाल में credentials environment variables में रहते हैं — code में कभी नहीं, git में कभी नहीं।

大多数有用的 API 都要求你表明身份。常见的模式有三种。无论哪种,凭据都应放在环境变量中 — 绝不写进代码,绝不提交到 git。

🔑
API Key
simplest — a shared secretसबसे सरल — साझा secret最简单 — 共享密钥
Sent as a query param or a header. Common with weather, translation and simple SaaS APIs. Rotate it from time to time.
Query param या header में भेजी जाती है। मौसम, अनुवाद और सरल SaaS APIs में आम। समय-समय पर बदलिए।
作为查询参数或请求头发送。常见于天气、翻译和简单的 SaaS API。要定期更换。
🎟️
Bearer Token
OAuth2 / JWT — modern standardOAuth2 / JWT — आधुनिक मानकOAuth2 / JWT — 现代标准
A token you get (often after login) and send in an Authorization: Bearer <token> header. Used by GitHub, OpenAI and many modern APIs.
एक token जो आपको मिलता है (अक्सर login के बाद) और Authorization: Bearer <token> header में भेजा जाता है। GitHub, OpenAI और कई आधुनिक APIs इसे लेते हैं।
你获得的一个令牌(通常在登录后),放在 Authorization: Bearer <token> 请求头中发送。GitHub、OpenAI 和许多现代 API 都在用。
👤
Basic Auth
username + password (HTTPS only!)username + password (सिर्फ़ HTTPS!)用户名 + 密码(仅限 HTTPS!)
Older style. The credentials are only base64-encoded, not encrypted — safe only over HTTPS. Still common with internal company APIs.
पुराना तरीका। Credentials सिर्फ़ base64-encoded होते हैं, encrypted नहीं — सिर्फ़ HTTPS पर सुरक्षित। Company के अंदरूनी APIs में अब भी आम।
较老的方式。凭据只是 base64 编码,并没有加密 — 只有在 HTTPS 下才安全。在公司内部 API 中仍很常见。
import os, requests

# 1) API key as query param - the API tells you the parameter name
r = requests.get(
    "https://api.openweathermap.org/data/2.5/weather",
    params={"q": "Mumbai", "appid": os.environ["OWM_KEY"]},
    timeout=5,
)
print("1) key in URL   :", r.status_code, r.json()["name"])

# 2) API key as header - many modern APIs prefer this
r = requests.get(
    "https://api.example.com/data",
    headers={"X-API-Key": os.environ["API_KEY"]},
    timeout=5,
)
print("2) key in header:", r.status_code, r.json())

# 3) Bearer token - GitHub, OpenAI, Stripe pattern
token = os.environ["GITHUB_TOKEN"]
r = requests.get(
    "https://api.github.com/user",
    headers={"Authorization": f"Bearer {token}"},
    timeout=5,
)
print("3) bearer token :", r.status_code, r.json()["login"])

# 4) Basic auth - requests has built-in support
r = requests.get(
    "https://api.example.com/private",
    auth=(os.environ["API_USER"], os.environ["API_PASSWORD"]),
    timeout=5,
)
print("4) basic auth   :", r.status_code, r.json())
print("   header sent  :", r.request.headers["Authorization"])
OUTPUT
1) key in URL : 200 Mumbai 2) key in header: 200 {'items': [1, 2, 3]} 3) bearer token : 200 dr-mohit-demo 4) basic auth : 200 {'secret': 'only for ada'} header sent : Basic YWRhOnMzY3JldA==

Look at the last line: YWRhOnMzY3JldA== is just ada:s3cret in base64. Anyone who sees it can decode it in a second — that is why Basic auth needs HTTPS.

आख़िरी line देखिए: YWRhOnMzY3JldA== बस base64 में ada:s3cret है। जो भी इसे देखे, एक सेकंड में decode कर सकता है — इसीलिए Basic auth को HTTPS चाहिए।

看最后一行:YWRhOnMzY3JldA== 只是 ada:s3cret 的 base64 形式。任何看到它的人一秒钟就能解码 — 所以 Basic 认证必须配合 HTTPS。

Figure 3 — Bearer Token Auth Flowचित्र 3 — Bearer Token Auth Flow图 3 — Bearer 令牌认证流程
Bearer token flowCLIENTCLIENT客户端your programआपका program你的程序AUTH SERVERAUTH SERVER认证服务器login endpointlogin endpoint登录端点API SERVERAPI SERVERAPI 服务器protected dataसुरक्षित data受保护的数据1. POST creds2. token3. GET /data + Bearer <token>4. JSON responseThe token is obtained ONCE, then reused for many API calls.Token एक बार मिलता है, फिर कई API calls में दोबारा लगता है।令牌只获取一次,然后在许多 API 调用中重复使用。When it expires, get a new one — often with a refresh token.Expire होने पर नया लीजिए — अक्सर refresh token से।过期后再获取新的 — 通常借助 refresh token。

Log in once, then send the token with every call.

एक बार login, फिर हर call के साथ token भेजिए।

登录一次,之后每次调用都带上令牌。

🔧
Fixed: os.environ["USER"] Is Your Login Nameसुधार: os.environ["USER"] आपका Login नाम है已修正:os.environ["USER"] 是你的登录名

The original Basic-auth example read the username from os.environ["USER"]. On Linux and macOS, USER is already set by the operating system to your login name — so the API gets the wrong user. Use your own names, such as API_USER and API_PASSWORD (as in the fixed code above). The run below shows the failure.

मूल Basic-auth example ने username os.environ["USER"] से पढ़ा। Linux और macOS पर USER पहले से operating system ने आपके login नाम पर set किया होता है — तो API को ग़लत user मिलता है। अपने नाम लीजिए, जैसे API_USER और API_PASSWORD (जैसा ऊपर के सुधरे code में)। नीचे का run विफलता दिखाता है।

原 Basic 认证示例从 os.environ["USER"] 读取用户名。在 Linux 和 macOS 上,USER 已经被操作系统设置为 你的登录名 — 于是 API 收到了错误的用户。请使用自己的变量名,例如 API_USER 和 API_PASSWORD(如上面修正后的代码)。下面的运行展示了失败情况。

import os, requests

# Danger: USER is already set by your operating system!
print("USER is:", os.environ["USER"])         # your login name, not the API user

r = requests.get("https://api.example.com/private",
                 auth=(os.environ["USER"], os.environ["API_PASSWORD"]), timeout=5)
print(r.status_code, r.json())

r = requests.get("https://api.github.com/user",
                 headers={"Authorization": "Bearer wrong-token"}, timeout=5)
print(r.status_code, r.json()["message"])
OUTPUT
USER is: mohit 401 {'error': 'unauthorized'} 401 Bad credentials
🔒
Never Commit SecretsSecrets कभी Commit न करें永远不要提交机密

Never paste an API key or token into your source code. Put it in an environment variable, a .env file listed in .gitignore, or a secrets manager. Load it with os.environ["KEY"] or python-dotenv. Keys leaked in git history are one of the most common security incidents on GitHub.

API key या token को अपने source code में कभी न चिपकाएँ। उसे environment variable, .gitignore में दर्ज .env file, या secrets manager में रखिए। os.environ["KEY"] या python-dotenv से load कीजिए। Git history में leak हुई keys GitHub पर सबसे आम security घटनाओं में से हैं।

永远不要 把 API 密钥或令牌粘贴进源代码。把它放在环境变量、已列入 .gitignore 的 .env 文件或密钥管理器中。用 os.environ["KEY"] 或 python-dotenv 加载。泄露在 git 历史中的密钥是 GitHub 上最常见的安全事件之一。


SectionSection第 09 节

Handling Status Codes and Errors 🚨Status Codes और Errors संभालना 🚨处理状态码与错误 🚨

The status code is your first check on every response. Ignoring it is how you get confusing bugs at 3 AM.

Status code हर response पर आपकी पहली जाँच है। इसे अनदेखा करना ही रात 3 बजे उलझाने वाले bugs की वजह बनता है।

状态码是每个响应的第一道检查。忽视它,就会在凌晨 3 点遇到让人摸不着头脑的 bug。

Figure 4 — HTTP Status Code Decision Treeचित्र 4 — HTTP Status Code Decision Tree图 4 — HTTP 状态码决策树
Status code treeRESPONSERESPONSE响应status code = ?status code = ?状态码 = ?2xx SUCCESS2xx सफल2xx 成功200, 201, 204use the datadata इस्तेमाल करें使用数据3xx REDIRECT3xx REDIRECT3xx 重定向301, 302, 304requests follows 301/302requests 301/302 follow करता हैrequests 会跟随 301/3024xx CLIENT4xx CLIENT4xx 客户端400, 401, 404, 429your request — fix itआपकी request — सुधारिए你的请求 — 修正它5xx SERVER5xx SERVER5xx 服务器500, 502, 503, 504their side — retry laterउनकी तरफ़ — बाद में retry对方问题 — 稍后重试200 OKeverything workedसब ठीक चला一切正常401 Unauthorizedmissing / bad tokentoken ग़ायब / ग़लत令牌缺失 / 错误404 Not Foundwrong URL / idग़लत URL / idURL / id 错误429 Too Manyrate limited — back offrate limit — रुकिए被限流 — 放慢速度Retry 5xx and 429 automatically. Do not retry other 4xx — the request itself is wrong.5xx और 429 को अपने-आप retry करें। बाक़ी 4xx retry न करें — request ही ग़लत है।自动重试 5xx 和 429。其他 4xx 不要重试 — 请求本身就是错的。

The first digit tells you whose problem it is.

पहला अंक बताता है कि समस्या किसकी है।

第一位数字告诉你是谁的问题。

import requests

r = requests.get("https://api.github.com/repos/does-not/exist", timeout=5)

# Option A: check the code manually
if r.status_code == 200:
    print(r.json())
elif r.status_code == 404:
    print("repo does not exist")
elif r.status_code == 429:
    print("rate limited, wait and retry")
else:
    print(f"unexpected: {r.status_code}")

# Option B: let requests raise on any 4xx/5xx
try:
    r = requests.get("https://api.github.com/repos/does-not/exist", timeout=5)
    r.raise_for_status()          # raises HTTPError on 4xx/5xx
    data = r.json()
except requests.HTTPError as e:
    print(f"HTTP failure: {e}")
except requests.Timeout:
    print("server took too long")
except requests.ConnectionError:
    print("can't reach server (DNS / offline)")

print("r.ok =", r.ok)
OUTPUT
repo does not exist HTTP failure: 404 Client Error: Not Found for url: https://api.github.com/repos/does-not/exist r.ok = False

The Timeout and ConnectionError branches never ran above. Here they are, triggered on purpose:

ऊपर Timeout और ConnectionError वाली branches कभी नहीं चलीं। यहाँ वे जान-बूझकर चलाई गई हैं:

上面的 Timeout 和 ConnectionError 分支都没有执行。这里我们故意触发它们:

import requests

for url in ["https://api.example.com/hang",            # never answers
            "https://no-such-host.invalid/"]:          # DNS fails
    try:
        requests.get(url, timeout=1.5)
    except requests.Timeout as e:
        print("Timeout        :", type(e).__name__)
    except requests.ConnectionError as e:
        print("ConnectionError:", type(e).__name__)
OUTPUT
Timeout : ReadTimeout ConnectionError: ConnectionError
🔧
Fixed: Redirects: 304 Is Not Followed, and HEAD Does Not Followसुधार: Redirects: 304 Follow नहीं होता, और HEAD Follow नहीं करता已修正:重定向:304 不会被跟随,HEAD 也不跟随

The original diagram said requests follows all 3xx "auto". It follows real redirects such as 301 and 302 (see r.history in the explorer below). But 304 Not Modified has no new location — it is a cache answer, returned as is. And requests.head() does not follow redirects unless you pass allow_redirects=True. Also, 429 is a 4xx code that you should retry (after waiting).

मूल चित्र में कहा गया कि requests हर 3xx को "auto" follow करता है। यह 301 और 302 जैसे असली redirects follow करता है (नीचे explorer में r.history देखें)। पर 304 Not Modified में कोई नई जगह नहीं होती — यह cache वाला जवाब है, जैसा है वैसा लौटता है। और requests.head() redirects follow नहीं करता जब तक आप allow_redirects=True न दें। साथ ही, 429 एक 4xx code है जिसे (इंतज़ार के बाद) retry करना चाहिए।

原图说 requests 会"自动"跟随所有 3xx。它确实会跟随 301、302 这样的真正重定向(见下方探索器中的 r.history)。但 304 Not Modified 没有新地址 — 它是缓存应答,会原样返回。而且 requests.head() 默认 不 跟随重定向,除非传入 allow_redirects=True。另外,429 虽是 4xx,却 应该 在等待后重试。

🚦 Simulator: Status Code Explorer🚦 Simulator: Status Code Explorer🚦 模拟器:状态码探索器

Click a status code. Each one was really requested, and the panel shows what requests did with it: r.ok, raise_for_status(), r.json() and redirects.

एक status code पर click कीजिए। हर एक सच में माँगा गया, और panel दिखाता है कि requests ने उसके साथ क्या किया: r.ok, raise_for_status(), r.json() और redirects।

点击一个状态码。每个都真实请求过,面板显示 requests 如何处理它:r.ok、raise_for_status()、r.json() 以及重定向。

Status Code ExplorerStatus Code Explorer状态码探索器15 codes · real responses15 codes · असली responses15 个状态码 · 真实响应

SectionSection第 10 节

Retries with Exponential Backoff ⏳Exponential Backoff के साथ Retries ⏳指数退避重试 ⏳

Networks fail. Servers hiccup. Rate limits happen. A good client retries temporary errors with a growing pause, so the server has room to recover. This is called exponential backoff.

Networks fail होते हैं। Servers अटकते हैं। Rate limits लगते हैं। अच्छा client अस्थायी errors को बढ़ते विराम के साथ retry करता है, ताकि server संभल सके। इसे exponential backoff कहते हैं।

网络会故障,服务器会卡顿,也会遇到限流。好的客户端会以逐渐变长的间隔重试 临时 错误,给服务器恢复的空间。这叫做 指数退避。

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

# Built-in retry - no manual loop needed
retry_policy = Retry(
    total=5,                      # up to 5 RETRIES (6 attempts in all)
    backoff_factor=1.0,           # sleeps between tries: 0, 2, 4, 8, 16 s
    status_forcelist=[429, 500, 502, 503, 504],
    allowed_methods=["GET", "HEAD", "PUT", "DELETE", "OPTIONS"],  # idempotent only
    respect_retry_after_header=True,
)

session = requests.Session()
session.mount("https://", HTTPAdapter(max_retries=retry_policy))

# Now every request through this session auto-retries transient errors
r = session.get("https://api.github.com/users/torvalds", timeout=10)
print(r.status_code)
OUTPUT
200
🔧
Fixed: Retry Counts, Backoff Delays and POSTसुधार: Retry गिनती, Backoff देरी और POST已修正:重试次数、退避延迟与 POST

Three corrections to the original. (1) total=5 means up to 5 retries — 6 attempts in all, not "at most 5 attempts". (2) With urllib3 2.8.0 the first retry happens at once; the sleeps are 0, 2, 4, 8, 16 s for backoff_factor=1.0, not 1, 2, 4, 8, 16 (the run below measures it). (3) The original put "POST" in allowed_methods. POST is not idempotent: if the server did the work but the reply was lost, a retry does it again — two orders, two payments. The default list leaves POST out on purpose.

मूल में तीन सुधार। (1) total=5 का मतलब है 5 तक retries — कुल 6 प्रयास, "ज़्यादा से ज़्यादा 5 प्रयास" नहीं। (2) urllib3 2.8.0 में पहला retry तुरंत होता है; backoff_factor=1.0 के लिए विराम 0, 2, 4, 8, 16 s हैं, 1, 2, 4, 8, 16 नहीं (नीचे का run इसे मापता है)। (3) मूल ने "POST" को allowed_methods में रखा। POST idempotent नहीं है: अगर server ने काम कर दिया पर जवाब खो गया, तो retry उसे फिर करता है — दो orders, दो payments। Default सूची जान-बूझकर POST को बाहर रखती है।

对原文的三处修正。(1) total=5 表示最多 5 次 重试 — 总共 6 次尝试,而不是"最多 5 次尝试"。(2) 在 urllib3 2.8.0 中,第一次重试会立即进行;backoff_factor=1.0 时的等待是 0、2、4、8、16 秒,而不是 1、2、4、8、16(下面的运行实测了这一点)。(3) 原文把 "POST" 放进了 allowed_methods。POST 不是幂等的:如果服务器已经完成了工作但回复丢失,重试会 再做一次 — 两个订单、两次付款。默认列表有意不包含 POST。

import time, requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

# A test endpoint that fails twice with 503, then succeeds
URL = "https://api.example.com/flaky/demo?fail=2&code=503"

retry = Retry(total=5, backoff_factor=0.5, status_forcelist=[429, 500, 502, 503, 504])
s = requests.Session()
s.mount("https://", HTTPAdapter(max_retries=retry))

t0 = time.perf_counter()
r = s.get(URL, timeout=5)
print(r.status_code, r.json(), f"after {time.perf_counter() - t0:.1f} s")

log = requests.get("https://api.example.com/flaky-log/demo", timeout=5).json()
gaps = [round(b - a, 1) for a, b in zip(log["t"], log["t"][1:])]
print("server saw", log["n"], "attempts; gaps between them:", gaps, "s")
OUTPUT
200 {'attempt': 3, 'status': 200} after 1.0 s server saw 3 attempts; gaps between them: [0.0, 1.0] s

🔁 Simulator: Retry Lab🔁 Simulator: Retry Lab🔁 模拟器:重试实验室

A test server fails a chosen number of times, then succeeds. The client uses Retry(total=3, backoff_factor=0.5). Every dot is a real attempt, placed at the time the server saw it. For POST, this server saves the order even when it answers with an error — just like a real server whose reply gets lost.

एक test server चुनी गई बार fail होता है, फिर सफल होता है। Client Retry(total=3, backoff_factor=0.5) लेता है। हर बिंदु एक असली प्रयास है, उसी समय पर जब server ने उसे देखा। POST के लिए यह server error देते हुए भी order save कर लेता है — बिल्कुल उस असली server की तरह जिसका जवाब खो जाता है।

一台测试服务器先失败指定的次数,然后成功。客户端使用 Retry(total=3, backoff_factor=0.5)。每个圆点都是一次真实尝试,放在服务器收到它的时间点上。对于 POST,这台服务器即使返回错误也会保存订单 — 就像回复丢失的真实服务器一样。

Retry LabRetry Lab重试实验室45 real runs · urllib3 2.8.045 असली runs · urllib3 2.8.045 次真实运行 · urllib3 2.8.0
methodmethod方法
errorerror错误
server fails firstserver पहले fail होता है服务器先失败
💡
Retry What, Exactly?ठीक-ठीक क्या Retry करें?到底该重试什么?

Retry network errors (timeouts, dropped connections), 429 Too Many Requests (wait for Retry-After) and 5xx server errors — these are usually temporary. Do not retry other 4xx errors (400, 401, 404): the request itself is wrong, so trying again only wastes time. And only retry non-idempotent requests like POST if the API gives you an idempotency key.

Network errors (timeouts, टूटे connections), 429 Too Many Requests (Retry-After तक रुकिए) और 5xx server errors retry कीजिए — ये आम तौर पर अस्थायी होते हैं। बाक़ी 4xx errors (400, 401, 404) retry न करें: request ही ग़लत है, दोबारा भेजना बस समय गँवाना है। और POST जैसी non-idempotent requests तभी retry करें जब API idempotency key देता हो।

重试 网络错误(超时、连接中断)、429 Too Many Requests(等待 Retry-After)和 5xx 服务器错误 — 它们通常是暂时的。不要重试其他 4xx 错误(400、401、404):请求本身就错了,重试只是浪费时间。像 POST 这样的非幂等请求,只有在 API 提供幂等键时才应重试。


SectionSection第 11 节

Sessions — Connection Reuse for Speed 🔌Sessions — Speed के लिए Connection दोबारा इस्तेमाल 🔌Session — 复用连接以提速 🔌

If you make more than one request to the same host, use a Session. It keeps the TCP + TLS connection open and reuses it, so later requests skip the connection setup (TCP handshake and TLS negotiation). A Session also keeps headers and cookies for you.

अगर आप एक ही host को एक से ज़्यादा requests भेजते हैं, तो Session लीजिए। यह TCP + TLS connection खुला रखकर दोबारा इस्तेमाल करता है, इसलिए बाद की requests connection बनाने का काम (TCP handshake और TLS negotiation) छोड़ देती हैं। Session आपके लिए headers और cookies भी याद रखता है।

如果你要向同一主机发送多个请求,就用 Session。它保持 TCP + TLS 连接打开并复用,后续请求就能跳过建立连接的步骤(TCP 握手和 TLS 协商)。Session 还会替你保存请求头和 Cookie。

❌ Fresh connection every timeहर बार नया connection每次都新建连接
StepStep步骤Example costउदाहरण लागत示例耗时
DNS lookupDNS lookupDNS 查询~20 ms
TCP handshakeTCP handshakeTCP 握手~30 ms
TLS handshakeTLS handshakeTLS 握手~80 ms
Actual GETअसली GET实际 GET~50 ms
Per requestप्रति request每个请求~180 ms
✅ Session (reused connection)Session (दोबारा इस्तेमाल connection)Session(复用连接)
StepStep步骤Example costउदाहरण लागत示例耗时
DNS lookupDNS lookupDNS 查询skipped (already connected)छूट गया (पहले से जुड़ा)跳过(已连接)
TCP handshakeTCP handshakeTCP 握手reusedदोबारा复用
TLS handshakeTLS handshakeTLS 握手reusedदोबारा复用
Actual GETअसली GET实际 GET~50 ms
Per requestप्रति request每个请求~50 ms
📏
These Costs Are Examplesये लागतें उदाहरण हैं这些耗时只是示例

The millisecond numbers above are typical examples for a server far away on the internet. Real costs depend on distance and network. The run below is on one machine, so the gap is smaller — but the number of connections tells the real story.

ऊपर के millisecond numbers internet पर दूर के server के लिए आम उदाहरण हैं। असली लागत दूरी और network पर निर्भर है। नीचे का run एक ही machine पर है, इसलिए अंतर छोटा है — पर connections की गिनती असली कहानी बताती है।

上面的毫秒数是访问互联网上远端服务器的典型示例。实际耗时取决于距离和网络。下面的运行在同一台机器上,所以差距较小 — 但连接数说明了真正的问题。

import time, requests

N = 100
def stats(): return requests.get("https://api.example.com/conns?reset=1", timeout=5).json()

stats()
# Bad - new connection every call
t0 = time.perf_counter()
for i in range(N):
    requests.get(f"https://api.github.com/repos/python/cpython/issues/{i + 1}", timeout=5)
t_bad = time.perf_counter() - t0
print(f"no session : {t_bad:.2f} s, connections opened: {stats()['connections'] - 1}")

# Good - one session, connections reused
token = "ghp_demo_token_123"
t0 = time.perf_counter()
with requests.Session() as s:
    s.headers.update({"Authorization": f"Bearer {token}"})
    for i in range(N):
        r = s.get(f"https://api.github.com/repos/python/cpython/issues/{i + 1}",
                  timeout=5)
t_good = time.perf_counter() - t0
print(f"session    : {t_good:.2f} s, connections opened: {stats()['connections'] - 1}")
print(f"speed-up   : {t_bad / t_good:.1f}x")
OUTPUT
no session : 0.86 s, connections opened: 100 session : 0.57 s, connections opened: 1 speed-up : 1.5x
🔧
Fixed: The "Bad" Loop Had No Timeout, and token Was Undefinedसुधार: "Bad" Loop में Timeout नहीं था, और token Undefined था已修正:"坏"循环没有超时,token 也未定义

The original "bad" loop called requests.get(...) with no timeout=, and the "good" loop used token without defining it, so the snippet could not run as written. It also started at issue number 0, which does not exist. The version above fixes all three and measures both loops.

मूल "bad" loop ने timeout= के बिना requests.get(...) बुलाया, और "good" loop ने token को define किए बिना इस्तेमाल किया, इसलिए snippet जैसा लिखा था वैसा चल नहीं सकता था। यह issue number 0 से भी शुरू होता था, जो मौजूद नहीं है। ऊपर का version तीनों सुधारता है और दोनों loops मापता है।

原"坏"循环调用 requests.get(...) 时没有 timeout=,"好"循环使用了未定义的 token,所以这段代码按原样无法运行。它还从编号 0 的 issue 开始,而该 issue 并不存在。上面的版本修正了这三点,并对两个循环都做了测量。

🔌 Simulator: Session Lab🔌 Simulator: Session Lab🔌 模拟器:Session 实验室

Choose how many requests to send. Each little block is one TCP + TLS connection the server really saw.

चुनिए कितनी requests भेजनी हैं। हर छोटा block एक TCP + TLS connection है जिसे server ने सच में देखा।

选择要发送多少个请求。每个小方块代表服务器真实看到的一个 TCP + TLS 连接。

Session LabSession LabSession 实验室real HTTPS runs · best of 3असली HTTPS runs · 3 में सबसे अच्छा真实 HTTPS 运行 · 3 次取最佳
requestsrequests请求数

SectionSection第 12 节

Pagination — Fetching Big Result Sets 📚Pagination — बड़े Results लाना 📚分页 — 获取大量结果 📚

APIs return data in pages — you rarely get 10,000 items in one call. There are three common styles:

APIs data pages में लौटाते हैं — एक call में 10,000 items शायद ही मिलते हैं। तीन आम तरीके हैं:

API 以 分页 形式返回数据 — 一次调用很少能拿到 10,000 条。常见的方式有三种:

📌 Pagination StylesPagination के तरीके分页方式
Style 1तरीका 1方式 1
Page numbers — ask for ?page=1, ?page=2 … until an empty page arrives.
Page numbers — ?page=1, ?page=2 … माँगिए जब तक ख़ाली page न आए।
页码 — 依次请求 ?page=1、?page=2 …,直到返回空页。
Style 2तरीका 2方式 2
Cursor / next URL — the response contains a next link (GitHub puts it in the Link header). Follow it until there is none.
Cursor / next URL — response में next link होता है (GitHub इसे Link header में रखता है)। जब तक हो, उसे follow कीजिए।
游标 / 下一页 URL — 响应里包含 next 链接(GitHub 把它放在 Link 响应头中)。一直跟随,直到没有为止。
Style 3तरीका 3方式 3
Offset / limit — ?offset=200&limit=100. Increase the offset until fewer results than the limit come back.
Offset / limit — ?offset=200&limit=100। Offset बढ़ाइए जब तक limit से कम results न आएँ।
偏移 / 限制 — ?offset=200&limit=100。不断增加 offset,直到返回的结果少于 limit。
🔧
Fixed: "Two Common Patterns" Listed Threeसुधार: "दो आम Patterns" में तीन गिनाए已修正:"两种常见模式"却列了三种

The original text said "two common patterns" and then listed three styles. There are three, as shown. The original code also used requests and os without importing them; the runnable version below adds the imports.

मूल text में "दो आम patterns" लिखा था और फिर तीन तरीके गिनाए। तीन ही हैं, जैसा दिखाया गया। मूल code ने requests और os को import किए बिना इस्तेमाल किया; नीचे का चलने वाला version imports जोड़ता है।

原文说"两种常见模式",却接着列出了三种。确实是三种,如上所示。原代码还在未导入的情况下使用了 requests 和 os;下面可运行的版本补上了导入。

import os, requests

def fetch_all_issues(repo: str, token: str) -> list[dict]:
    session = requests.Session()
    session.headers.update({"Authorization": f"Bearer {token}"})

    all_issues, page = [], 1
    while True:
        r = session.get(f"https://api.github.com/repos/{repo}/issues",
                        params={"page": page, "per_page": 100, "state": "all"},
                        timeout=10)
        r.raise_for_status()
        batch = r.json()
        print(f"page {page}: {len(batch)} items")
        if not batch:                    # empty page = we're done
            break
        all_issues.extend(batch)
        page += 1
    return all_issues

issues = fetch_all_issues("python/cpython", os.environ["GITHUB_TOKEN"])
print(f"fetched {len(issues)} issues")
OUTPUT
page 1: 100 items page 2: 100 items page 3: 37 items page 4: 0 items fetched 237 issues

This works, but it spends one extra request on the empty page. GitHub sends a Link header with the next URL, and requests parses it for you as r.links:

यह चलता है, पर ख़ाली page पर एक अतिरिक्त request ख़र्च करता है। GitHub अगले URL के साथ Link header भेजता है, और requests उसे आपके लिए r.links के रूप में parse करता है:

这样可行,但会在空页上多花一次请求。GitHub 会发送带有下一页 URL 的 Link 响应头,requests 会把它解析为 r.links:

import os, requests

def fetch_all_issues(repo: str, token: str) -> list[dict]:
    s = requests.Session()
    s.headers.update({"Authorization": f"Bearer {token}"})
    url = f"https://api.github.com/repos/{repo}/issues"
    params = {"per_page": 100, "state": "all"}
    items = []
    while url:
        r = s.get(url, params=params, timeout=10)
        r.raise_for_status()
        items.extend(r.json())
        print(f"got {len(r.json())} items, next ->", r.links.get("next", {}).get("url"))
        url = r.links.get("next", {}).get("url")   # GitHub's Link header
        params = None                              # the next URL already has them
    return items

print("fetched", len(fetch_all_issues("python/cpython", os.environ["GITHUB_TOKEN"])))
OUTPUT
got 100 items, next -> https://api.github.com/repos/python/cpython/issues?per_page=100&state=all&page=2 got 100 items, next -> https://api.github.com/repos/python/cpython/issues?per_page=100&state=all&page=3 got 37 items, next -> None fetched 237
🐙
GitHub Issues Include Pull RequestsGitHub Issues में Pull Requests भी होते हैंGitHub 的 issues 包含拉取请求

GitHub's issues endpoint also returns pull requests (they carry a pull_request key). If you want only real issues, skip items that have that key.

GitHub का issues endpoint pull requests भी लौटाता है (उनमें pull_request key होती है)। अगर सिर्फ़ असली issues चाहिए, तो उस key वाले items छोड़ दीजिए।

GitHub 的 issues 端点也会返回拉取请求(它们带有 pull_request 键)。如果只想要真正的 issue,就跳过带有该键的条目。

📚 Simulator: Pagination Stepper📚 Simulator: Pagination Stepper📚 模拟器:分页单步演示

237 items wait on the server. Pick a style and a page size, then step through the real requests. Watch how each style knows when to stop.

Server पर 237 items इंतज़ार कर रहे हैं। एक तरीका और page size चुनिए, फिर असली requests step-by-step देखिए। देखिए हर तरीका कैसे जानता है कि कब रुकना है।

服务器上有 237 条数据。选择一种方式和每页大小,然后逐步查看真实请求。观察每种方式如何判断何时停止。

Pagination StepperPagination Stepper分页单步演示237 items · real requests237 items · असली requests237 条 · 真实请求
styleतरीका方式
per pageप्रति page每页

SectionSection第 13 节

Common Pitfalls (and Fixes) 🕳️आम ग़लतियाँ (और सुधार) 🕳️常见陷阱(及修正)🕳️

Mistakeग़लती错误What happensक्या होता है会发生什么Fixसुधार修正
No timeouttimeout नहीं没有 timeoutProgram can hang for ever on a silent serverचुप server पर program हमेशा अटक सकता है遇到不回应的服务器,程序可能永远挂起Always pass timeout=5 (or what your service needs)हमेशा timeout=5 (या जो service को चाहिए)始终传入 timeout=5(或服务所需的值)
Ignoring status_codestatus_code अनदेखा忽略 status_codeAn HTML error page is parsed as JSON → JSONDecodeErrorHTML error page को JSON मानकर parse → JSONDecodeError把 HTML 错误页当成 JSON 解析 → JSONDecodeErrorCheck r.ok or call r.raise_for_status()r.ok जाँचिए या r.raise_for_status() बुलाइए检查 r.ok 或调用 r.raise_for_status()
Hard-coded API keyCode में लिखी API key硬编码 API 密钥Leaked into git history, credentials stolenGit history में leak, credentials चोरी泄露到 git 历史,凭据被盗Environment variables + .gitignoreEnvironment variables + .gitignore环境变量 + .gitignore
requests.get() in a big loopबड़े loop में requests.get()在大循环里直接 requests.get()A new connection for every request — slowerहर request के लिए नया connection — धीमा每个请求都新建连接 — 更慢Use requests.Session()requests.Session() लीजिए使用 requests.Session()
String-built URLsString जोड़कर बने URLs字符串拼接的 URLSpaces / & / + / # break the querySpaces / & / + / # query तोड़ देते हैं空格 / & / + / # 会破坏查询Pass params={...}params={...} दीजिए传入 params={...}
data= instead of json=json= की जगह data=用 data= 代替 json=Sent as a form, not JSON — the API sees no JSONForm की तरह गया, JSON नहीं — API को JSON नहीं मिलता以表单发送而非 JSON — API 收不到 JSONUse json={...} for JSON APIsJSON APIs के लिए json={...}JSON API 用 json={...}
Optional field read with [key]Optional field [key] से पढ़ना用 [key] 读取可选字段KeyError if missing; None if nullग़ायब हो तो KeyError; null हो तो None缺失时 KeyError;为 null 时得到 NoneUse data.get(key) or defaultdata.get(key) or default लीजिए使用 data.get(key) or default
Retrying all 4xx errors, or POSTहर 4xx error, या POST retry करना重试所有 4xx 错误或 POSTWasted calls, possible bans, duplicate ordersबेकार calls, ban का ख़तरा, दोहरे orders浪费调用、可能被封禁、重复下单Retry 429, 5xx and network errors on idempotent methodsIdempotent methods पर 429, 5xx और network errors retry只对幂等方法重试 429、5xx 和网络错误
Very old XML parser setupबहुत पुराना XML parser非常旧的 XML 解析器Old expat (< 2.4.1) can be hit by "billion laughs"पुराना expat (< 2.4.1) "billion laughs" का शिकार हो सकता है旧版 expat(< 2.4.1)可能遭受"billion laughs"攻击Keep Python up to date; defusedxml for hostile XMLPython नया रखें; शत्रुतापूर्ण XML के लिए defusedxml保持 Python 更新;恶意 XML 用 defusedxml
🔧
Fixed: Two Rows Correctedसुधार: दो Rows सुधारी गईं已修正:修正了两行

The original said a missing optional field gives "KeyError on … None" — those are two different cases (see Section 06). And it said parsing untrusted XML with the standard library opens an "XXE" hole; as explained in Section 07, modern ElementTree does not expand external entities. We also added the very common data= vs json= mix-up, which the Bug Doctor below shows.

मूल में कहा गया कि ग़ायब optional field से "KeyError on … None" होता है — ये दो अलग मामले हैं (Section 06 देखें)। और कहा गया कि standard library से अविश्वसनीय XML parse करने पर "XXE" छेद खुलता है; Section 07 में समझाए अनुसार आधुनिक ElementTree external entities नहीं फैलाता। हमने बहुत आम data= बनाम json= गड़बड़ी भी जोड़ी, जिसे नीचे Bug Doctor दिखाता है।

原文说缺失的可选字段会导致"KeyError on … None"— 这其实是两种不同的情况(见第 06 节)。原文还说用标准库解析不可信 XML 会产生"XXE"漏洞;如第 07 节所述,现代 ElementTree 不会展开外部实体。我们还补充了非常常见的 data= 与 json= 混用问题,下面的 Bug 医生会演示。


SectionSection第 14 节

Real-World Example — Complete GitHub Repo Analyzer 🐙असली उदाहरण — पूरा GitHub Repo Analyzer 🐙实战示例 — 完整的 GitHub 仓库分析器 🐙

All the pieces together: a session, auth, timeouts, retries, error handling, JSON parsing and a typed dataclass — in one small, solid client. We added a fourth repo that does not exist, to show the error path.

सारे टुकड़े एक साथ: session, auth, timeouts, retries, error handling, JSON parsing और typed dataclass — एक छोटे, मज़बूत client में। Error वाला रास्ता दिखाने के लिए हमने चौथा repo जोड़ा जो मौजूद नहीं है।

所有部件合在一起:Session、认证、超时、重试、错误处理、JSON 解析和带类型的 dataclass — 组成一个小巧而可靠的客户端。我们加了第四个不存在的仓库,用来展示出错路径。

import os
from dataclasses import dataclass
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

BASE = "https://api.github.com"

@dataclass(frozen=True)
class RepoStats:
    full_name: str
    stars: int
    forks: int
    open_issues: int
    language: str | None
    top_contributors: list[str]

def build_session(token: str) -> requests.Session:
    retry = Retry(total=5, backoff_factor=1.0,
                  status_forcelist=[429, 500, 502, 503, 504])
    s = requests.Session()
    s.mount("https://", HTTPAdapter(max_retries=retry))
    s.headers.update({
        "Authorization": f"Bearer {token}",
        "Accept":        "application/vnd.github+json",
        "User-Agent":    "repo-analyzer/1.0",
    })
    return s

def analyze_repo(session: requests.Session, repo: str) -> RepoStats | None:
    try:
        r = session.get(f"{BASE}/repos/{repo}", timeout=10)
        r.raise_for_status()
        info = r.json()

        c = session.get(f"{BASE}/repos/{repo}/contributors",
                        params={"per_page": 5}, timeout=10)
        c.raise_for_status()
        top = [u["login"] for u in c.json()]

        return RepoStats(
            full_name        = info["full_name"],
            stars            = info["stargazers_count"],
            forks            = info["forks_count"],
            open_issues      = info["open_issues_count"],
            language         = info.get("language"),
            top_contributors = top,
        )
    except requests.HTTPError as e:
        print(f"[{repo}] HTTP error: {e}")
    except (requests.Timeout, requests.ConnectionError):
        print(f"[{repo}] network problem")
    return None

# -- Run it ------------------------------------------------
REPOS = ["python/cpython", "pallets/flask", "psf/requests", "no/such-repo"]
with build_session(os.environ["GITHUB_TOKEN"]) as s:
    for repo in REPOS:
        stats = analyze_repo(s, repo)
        if stats:
            print(f"\n{stats.full_name}")
            print(f"  stars {stats.stars:,}   forks {stats.forks:,}   issues {stats.open_issues:,}")
            print(f"  language: {stats.language}")
            print(f"  top:      {', '.join(stats.top_contributors)}")
OUTPUT
python/cpython stars 70,412 forks 32,510 issues 9,104 language: Python top: vstinner, serhiy-storchaka, gvanrossum, benjaminp, birkenfeld pallets/flask stars 70,180 forks 16,590 issues 8 language: Python top: davidism, mitsuhiko, untitaker, ThiefMaster, rduplain psf/requests stars 53,060 forks 9,580 issues 247 language: Python top: kennethreitz, Lukasa, sigmavirus24, nateprewitt, slingamn [no/such-repo] HTTP error: 404 Client Error: Not Found for url: https://api.github.com/repos/no/such-repo
🔧
Fixed: Snapshot Numbers, and What "open_issues" Countsसुधार: Snapshot Numbers, और "open_issues" क्या गिनता है已修正:快照数字,以及 "open_issues" 统计的是什么

The original output printed fixed star counts and contributor lists as if they were facts; they were a snapshot and are out of date. Our numbers come from the stand-in server. Also note: GitHub's open_issues_count counts open issues plus open pull requests, so it is not just "issues".

मूल output ने तय star counts और contributor सूचियाँ ऐसे print कीं जैसे वे तथ्य हों; वे snapshot थीं और पुरानी हो चुकी हैं। हमारे numbers stand-in server से हैं। ध्यान दें: GitHub का open_issues_count खुले issues और खुले pull requests दोनों गिनता है, इसलिए यह सिर्फ़ "issues" नहीं है।

原输出把固定的星标数和贡献者列表当作事实打印出来;它们只是快照,已经过时。我们的数字来自替身服务器。另请注意:GitHub 的 open_issues_count 统计的是未关闭的 issue 加上未合并的拉取请求,所以不仅仅是"issue"。

🏆
What This Client Does Rightयह Client क्या सही करता है这个客户端做对了什么

One session with reused connections. Token from an environment variable — no leaks. Automatic retries on 429 and 5xx with backoff, never for POST. A timeout on every call. Clear error handling — the missing repo did not crash the program. Data lands in a typed dataclass. This is the shape of most real API clients you will write.

दोबारा इस्तेमाल होने वाले connections वाला एक session। Environment variable से token — कोई leak नहीं। 429 और 5xx पर backoff के साथ अपने-आप retries, POST के लिए कभी नहीं। हर call पर timeout। साफ़ error handling — ग़ायब repo ने program crash नहीं किया। Data एक typed dataclass में आता है। आप जो असली API clients लिखेंगे, उनमें से ज़्यादातर का यही आकार होगा।

一个复用连接的 Session。令牌来自环境变量 — 不会泄露。对 429 和 5xx 自动退避重试,且绝不重试 POST。每个调用都有超时。清晰的错误处理 — 不存在的仓库没有让程序崩溃。数据落入带类型的 dataclass。你以后写的大多数真实 API 客户端都是这个样子。


SectionSection第 15 节

Bug Doctor, Golden Rules and Quiz 🏆Bug Doctor, सुनहरे नियम और Quiz 🏆Bug 医生、黄金法则与小测验 🏆

🩺 Simulator: Bug Doctor🩺 Simulator: Bug Doctor🩺 模拟器:Bug 医生

Six real API mistakes. Buggy code on the left, fixed code on the right, changed lines marked. Every output is from a real run (the hanging program was stopped after 4 seconds).

छह असली API ग़लतियाँ। बाईं ओर buggy code, दाईं ओर ठीक किया हुआ, बदली lines चिह्नित। हर output असली run से है (अटका program 4 सेकंड बाद रोका गया)।

六个真实的 API 错误。左边是有 bug 的代码,右边是修正后的代码,改动的行已标出。每个输出都来自真实运行(挂起的程序在 4 秒后被停止)。

Bug DoctorBug DoctorBug 医生6 bugs · real outputs6 bugs · असली outputs6 个 bug · 真实输出
❌ BuggyBuggy有 bug
✅ Fixedठीक किया已修正
📡 Working with APIs — Rules to RememberAPIs के साथ काम — याद रखने वाले नियम使用 API — 必须记住的规则
1
Always set timeout=. Without one, a silent server can hang your program for ever. Start with 5–10 seconds for reads.
हमेशा timeout= लगाइए। इसके बिना चुप server आपका program हमेशा के लिए अटका सकता है। पढ़ने के लिए 5–10 सेकंड से शुरू कीजिए।
始终设置 timeout=。没有它,一个不回应的服务器可能让程序永远挂起。读取操作可从 5–10 秒开始。
2
Always check the status code — with r.raise_for_status() or by looking at r.status_code. Assuming success leads to confusing errors deep in your parser.
हमेशा status code जाँचिए — r.raise_for_status() से या r.status_code देखकर। सफलता मान लेना parser में गहरे उलझाने वाले errors देता है।
始终检查状态码 — 用 r.raise_for_status() 或查看 r.status_code。想当然地认为成功,会在解析器深处引发难懂的错误。
3
Use requests.Session() when you make more than one call to the same host. It reuses connections and keeps shared headers in one place.
एक ही host को एक से ज़्यादा calls करते समय requests.Session() लीजिए। यह connections दोबारा इस्तेमाल करता है और साझा headers एक जगह रखता है।
向同一主机发送多次调用时,使用 requests.Session()。它复用连接,并把公共请求头集中在一处。
4
Never build URLs by joining strings. Pass params={} for query strings and json={} for JSON bodies, and let requests do the encoding.
Strings जोड़कर URLs कभी न बनाइए। Query strings के लिए params={} और JSON bodies के लिए json={} दीजिए, encoding requests को करने दीजिए।
永远不要拼接字符串来构造 URL。查询字符串用 params={},JSON 请求体用 json={},让 requests 负责编码。
5
Keep secrets in environment variables with clear names like API_USER — never in code, never in git. Replace any key that leaks at once.
Secrets environment variables में रखिए, API_USER जैसे साफ़ नामों के साथ — code में कभी नहीं, git में कभी नहीं। जो key leak हो, उसे तुरंत बदलिए।
把机密放在环境变量里,用 API_USER 这样清晰的名字 — 绝不写进代码,绝不提交到 git。任何泄露的密钥要立即更换。
6
Retry only temporary errors — 429, 500, 502, 503, 504 and network failures — with exponential backoff, honouring Retry-After. Do not retry other 4xx errors, and do not auto-retry POST.
सिर्फ़ अस्थायी errors retry कीजिए — 429, 500, 502, 503, 504 और network failures — exponential backoff के साथ, Retry-After मानते हुए। बाक़ी 4xx retry न करें, और POST को अपने-आप retry न करें।
只重试临时性错误 — 429、500、502、503、504 和网络故障 — 使用指数退避并遵守 Retry-After。不要重试其他 4xx,也不要自动重试 POST。
7
Treat outside XML with care. Keep Python and its expat library up to date; use defusedxml as an extra guard for XML from untrusted sources.
बाहरी XML सावधानी से संभालिए। Python और उसकी expat library नई रखिए; अविश्वसनीय स्रोतों के XML के लिए defusedxml को अतिरिक्त बचाव की तरह लीजिए।
谨慎对待外部 XML。保持 Python 及其 expat 库为最新版本;对来自不可信来源的 XML,用 defusedxml 作为额外防护。
8
Model the response with a dataclass or TypedDict. Raw dicts spread through your code and make every part fragile when the API changes. One typed boundary is much easier to update than fifty.
Response को dataclass या TypedDict में ढालिए। Raw dicts पूरे code में फैल जाते हैं और API बदलने पर हर हिस्से को नाज़ुक बना देते हैं। पचास जगहों की बजाय एक typed सीमा को बदलना कहीं आसान है।
用 dataclass 或 TypedDict 为响应建模。原始字典会散布在代码各处,API 一变,处处都脆弱。改一处有类型的边界,远比改五十处容易。
🔧
Fixed: Rules 3 and 7 Correctedसुधार: नियम 3 और 7 सुधारे गए已修正:规则 3 和 7 已修正

Rule 3 promised "~130 ms" saved per request — that number depends on distance and network, so we removed it. Rule 7 said to always replace the standard xml module because "XXE … attacks are real"; with current Python the standard ElementTree is not open to XXE (Section 07). The rule now says what actually matters.

नियम 3 ने हर request पर "~130 ms" बचत का वादा किया — यह number दूरी और network पर निर्भर है, इसलिए हमने हटाया। नियम 7 ने कहा कि standard xml module को हमेशा बदलिए क्योंकि "XXE … attacks असली हैं"; मौजूदा Python में standard ElementTree XXE के लिए खुला नहीं है (Section 07)। नियम अब वही कहता है जो सच में मायने रखता है।

规则 3 承诺每个请求节省"约 130 毫秒"— 这个数字取决于距离和网络,所以我们删去了。规则 7 说要始终替换标准 xml 模块,因为"XXE … 攻击是真实的";在当前的 Python 中,标准 ElementTree 并不受 XXE 影响(第 07 节)。现在的规则说的是真正重要的事。

Quick Quiz — 8 Questionsछोटा Quiz — 8 सवाल小测验 — 8 道题

🏆 QuizPick an answer to see why.जवाब चुनें और कारण देखें।选一个答案,看看原因。
🎯
Key Takeawayमुख्य सीख关键要点

An API call is a URL, a method and some data; the answer is a status code and some data. Make it reliable with five habits: a timeout on every call, a status check on every response, params=/json= instead of hand-built strings, secrets in environment variables, and retries only for temporary errors on idempotent requests. Wrap it all in a Session and a typed model.

API call एक URL, एक method और कुछ data है; जवाब एक status code और कुछ data है। पाँच आदतों से इसे भरोसेमंद बनाइए: हर call पर timeout, हर response पर status जाँच, हाथ से बनी strings की जगह params=/json=, secrets environment variables में, और retries सिर्फ़ idempotent requests की अस्थायी errors पर। सब कुछ एक Session और typed model में लपेटिए।

一次 API 调用就是 URL、方法和一些数据;回答是状态码和一些数据。用五个习惯让它可靠:每个调用都设 timeout,每个响应都检查状态码,用 params=/json= 代替手工拼接字符串,机密放在环境变量里,只对幂等请求的临时错误进行重试。最后用 Session 和带类型的模型把一切包起来。

🐍 Try Python Yourself

Write, run and visualize Python step by step. No install needed.