Другая сторона API
Программы на Python — это не только серверы: они тоже вызывают API — получают данные, общаются с сервисами, интегрируются со сторонними системами. Работа с urllib.request из стандартной библиотеки сводится к трём привычкам:
1. отправить запрос (urlopen для простого GET, объект Request для полного контроля над методом, заголовками и телом),
2. открывать ответ в блоке with, чтобы он всегда закрывался,
3. помнить, что ответ 4xx или 5xx выбрасывает HTTPError — перехватывайте его там, где плохой код состояния — это случай, который вы обрабатываете.
Примеры ниже обращаются к локальному тестовому серверу, поэтому их вывод стабилен — а код клиента в точности такой же, какой вы направили бы на настоящий URL. (Популярный в экосистеме пакет requests оборачивает те же идеи в более удобный API; здесь всё остаётся в рамках стандартной библиотеки.)
GET и декодирование
Повседневная задача: выполнить GET по URL и декодировать JSON-тело. json.loads(resp.read()) даёт обычные данные Python, а dataclass превращает каждый словарь в типизированную запись с помощью User(**item).
Получить список в виде записей
Тело ответа разбирается в список записей User, а дальше это обычные данные Python — их перебирают в цикле и выводят поле за полем.
Вывод: полная программа на Python
import json
import threading
import urllib.request
from dataclasses import dataclass
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
@dataclass
class User:
id: int
name: str
class ListHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(b'[{"id": 1, "name": "Ana"}, {"id": 2, "name": "Ben"}]')
def log_message(self, fmt, *args):
pass
server = ThreadingHTTPServer(("127.0.0.1", 0), ListHandler)
base = f"http://127.0.0.1:{server.server_address[1]}"
threading.Thread(target=server.serve_forever, daemon=True).start()
with urllib.request.urlopen(base + "/users") as resp:
users = [User(**item) for item in json.loads(resp.read())]
for user in users:
print(f"пользователь {user.id}: {user.name}")
server.shutdown()
POST с JSON-телом
Отправка данных — зеркальное отражение: преобразуйте запись в байты JSON, отправьте их методом POST с правильным типом содержимого — а затем разберите ответ сервера, который здесь содержит id, назначенный сервером.
Отправить запись, прочитать ответ
Запись уходит в виде JSON, а обратно приходит созданный User(id=7, name='Cleo') — полный путь записи API-клиента в одной небольшой программе.
Вывод: полная программа на Python
import json
import threading
import urllib.request
from dataclasses import dataclass, asdict
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
@dataclass
class User:
id: int
name: str
class CreateHandler(BaseHTTPRequestHandler):
def do_POST(self):
length = int(self.headers.get("Content-Length", 0))
data = json.loads(self.rfile.read(length))
data["id"] = 7 # сервер назначает id
self.send_response(201)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write((json.dumps(data) + "\n").encode())
def log_message(self, fmt, *args):
pass
server = ThreadingHTTPServer(("127.0.0.1", 0), CreateHandler)
base = f"http://127.0.0.1:{server.server_address[1]}"
threading.Thread(target=server.serve_forever, daemon=True).start()
payload = json.dumps(asdict(User(id=0, name="Cleo"))).encode()
req = urllib.request.Request(base + "/users", data=payload,
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req) as resp:
created = User(**json.loads(resp.read()))
print("статус:", resp.status, resp.reason)
print("создан:", created)
server.shutdown()