Cealaltă parte a API-ului
Programele Python nu sunt doar servere — ele apelează și API-uri: obțin date, comunică cu servicii, se integrează cu terți. Modulul urllib.request din biblioteca standard înseamnă trei obiceiuri:
1. faci cererea (urlopen pentru un GET simplu, un obiect Request pentru control complet asupra metodei, antetelor și corpului),
2. deschizi răspunsul într-un bloc with, ca să fie mereu închis,
3. ții minte că un răspuns 4xx sau 5xx ridică HTTPError — prinde această excepție acolo unde un cod de stare greșit este un caz pe care îl tratezi.
Demonstrațiile de mai jos apelează un server local de test, așa că ieșirile sunt stabile — codul clientului este exact cel pe care l-ai îndrepta spre un URL real. (Popularul pachet requests din ecosistem împachetează aceleași idei într-un API mai prietenos; tot ce este aici rămâne în biblioteca standard.)
GET și decodare
Sarcina de zi cu zi: faci GET la un URL și decodezi corpul JSON. json.loads(resp.read()) dă date Python simple, iar o clasă de date (dataclass) transformă fiecare dicționar într-o înregistrare cu tipuri, cu User(**item).
Obține o listă de înregistrări
Corpul răspunsului este parsat într-o listă de înregistrări User, iar de acolo sunt date Python obișnuite — parcurse într-o buclă și afișate câmp cu câmp.
Ieșire: program Python complet
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"utilizator {user.id}: {user.name}")
server.shutdown()
POST cu un corp JSON
Trimiterea datelor este imaginea în oglindă: transformi înregistrarea în octeți JSON, îi trimiți prin POST cu tipul de conținut potrivit — apoi parsezi răspunsul serverului, care aici conține id-ul atribuit de server.
Trimite o înregistrare, citește răspunsul
Înregistrarea pleacă sub formă de JSON și se întoarce utilizatorul creat, User(id=7, name='Cleo') — întregul drum de scriere al unui client API într-un singur program mic.
Ieșire: program Python complet
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 # serverul atribuie id-ul
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("stare:", resp.status, resp.reason)
print("creat:", created)
server.shutdown()