Witaj z powrotem, @name! Darwin tutaj z kolejną lekcją o przepływie danych.
Nauczyłeś/aś się formatów danych (JSON, CSV, XML) i protokołu HTTP (GET, POST, PUT, DELETE). Teraz czas na REST API - standardową architekturę budowania API, którą używa się na całym świecie! 🌐
Analogia Safari: HTTP to system kurierski. REST API to standardowe procedury - zasady, według których organizujesz stacje badawcze, katalogi danych i komunikację między nimi. Dzięki tym zasadom każdy biolog, niezależnie skąd pochodzi, wie jak pobrać dane o lwach:
GET /api/species/lion 📋REST (REpresentational State Transfer) to styl architektury dla API - zbiór zasad i konwencji, jak projektować API wykorzystujące HTTP.
API (Application Programming Interface) to interfejs programistyczny - sposób, w jaki aplikacje rozmawiają ze sobą.
REST API = HTTP API zaprojektowane według zasad REST
Bez standardów każde API wyglądałoby inaczej:
/getLion?id=123/fetchLionData/lion-information/get/api/v1/animals/lion/retrieveZ REST wszyscy wiedzą:
GET /api/species/lion - pobierz lwaPOST /api/species - utwórz gatunekDELETE /api/species/lion - usuń lwaPrzewidywalne, spójne, intuicyjne! 🎯
W REST wszystko to zasób (resource) - obiekt, encja, dane.
Dobre ✅ - rzeczowniki:
/species - gatunek/observations - obserwacje/habitats - siedliska/researchers - badaczeZłe ❌ - czasowniki:
/getSpecies/createObservation/deleteHabitatAkcja określona przez metodę HTTP, nie URL!
| Metoda | Akcja | Przykład | |--------|-------|----------| | GET | Pobierz zasób/zasoby |
GET /species - lista, GET /species/lion - jeden |
| POST | Utwórz nowy zasób | POST /species - dodaj gatunek |
| PUT | Zastąp cały zasób | PUT /species/lion - zastąp dane lwa |
| PATCH | Zaktualizuj część zasobu | PATCH /species/lion - zmień populację |
| DELETE | Usuń zasób | DELETE /species/lion - usuń lwa |Każde zapytanie musi zawierać wszystkie informacje potrzebne do jego obsłużenia. Serwer nie pamięta poprzednich zapytań.
Przykład:
1# Każde zapytanie zawiera token autoryzacji
2GET /api/species/lion
3Authorization: Bearer abc123
4
5GET /api/observations
6Authorization: Bearer abc123Serwer nie wie "kto to wysłał wcześniej" - każde zapytanie samodzielne!
Zasoby mogą być zagnieżdżone hierarchicznie:
1/species/lion # Gatunek: lew
2/species/lion/observations # Obserwacje lwa
3/species/lion/observations/123 # Konkretna obserwacja lwa #123
4
5/habitats/serengeti # Siedlisko: Serengeti
6/habitats/serengeti/species # Gatunki w SerengetiZasada:
/zasób/id/podzasób/idAPI ewoluuje - wersjonowanie zapobiega zepsuciu starych klientów:
1/api/v1/species
2/api/v2/speciesZmiana wersji gdy:
1# LIST - lista wszystkich gatunków
2GET /api/species
3Response: [{"id": "lion", "name": "Lew"}, {"id": "elephant", "name": "Słoń"}]
4
5# GET - jeden gatunek
6GET /api/species/lion
7Response: {"id": "lion", "scientific_name": "Panthera leo", "population": 120}
8
9# CREATE - utwórz nowy gatunek
10POST /api/species
11Body: {"scientific_name": "Gorilla gorilla", "population": 230}
12Response: {"id": "gorilla", "scientific_name": "Gorilla gorilla", "population": 230}
13
14# UPDATE (całość) - zastąp gatunek
15PUT /api/species/lion
16Body: {"scientific_name": "Panthera leo", "population": 125, "habitat": "savanna"}
17Response: {"id": "lion", ...}
18
19# UPDATE (część) - zmień tylko populację
20PATCH /api/species/lion
21Body: {"population": 125}
22Response: {"id": "lion", "population": 125, ...}
23
24# DELETE - usuń gatunek
25DELETE /api/species/lion
26Response: 204 No Content (lub 200 z potwierdzeniem)1# Lista obserwacji dla lwa
2GET /api/species/lion/observations
3Response: [
4 {"id": "obs1", "date": "2024-01-15", "count": 12},
5 {"id": "obs2", "date": "2024-01-20", "count": 8}
6]
7
8# Konkretna obserwacja lwa
9GET /api/species/lion/observations/obs1
10Response: {"id": "obs1", "species": "lion", "date": "2024-01-15", "count": 12}
11
12# Dodaj obserwację dla lwa
13POST /api/species/lion/observations
14Body: {"date": "2024-01-25", "count": 10, "location": "Serengeti"}
15Response: {"id": "obs3", "species": "lion", ...}1# Gatunki zagrożone
2GET /api/species?endangered=true
3
4# Gatunki w sawannie z populacją > 100
5GET /api/species?habitat=savanna&min_population=100
6
7# Obserwacje z zakresu dat
8GET /api/observations?start_date=2024-01-01&end_date=2024-01-311# Sortuj według populacji rosnąco
2GET /api/species?sort=population
3
4# Sortuj według populacji malejąco
5GET /api/species?sort=-population
6
7# Sortuj po nazwie, potem populacji
8GET /api/species?sort=name,population1# Strona 1, 20 wyników na stronę
2GET /api/species?page=1&per_page=20
3
4# Alternatywnie: offset i limit
5GET /api/species?offset=0&limit=20
6
7# Odpowiedź z metadanymi paginacji:
8{
9 "data": [...],
10 "pagination": {
11 "current_page": 1,
12 "per_page": 20,
13 "total_items": 150,
14 "total_pages": 8
15 }
16}1# Tylko nazwa i populacja
2GET /api/species?fields=name,population
3
4Response: [
5 {"name": "Lew", "population": 120},
6 {"name": "Słoń", "population": 450}
7]1{
2 "id": "lion",
3 "scientific_name": "Panthera leo",
4 "common_name": "Lew",
5 "population": 120,
6 "habitat": "sawanna",
7 "endangered": true,
8 "created_at": "2024-01-01T10:00:00Z",
9 "updated_at": "2024-01-15T14:30:00Z"
10}1{
2 "data": [
3 {"id": "lion", "name": "Lew", "population": 120},
4 {"id": "elephant", "name": "Słoń", "population": 450}
5 ],
6 "pagination": {
7 "page": 1,
8 "per_page": 20,
9 "total": 45,
10 "pages": 3
11 },
12 "links": {
13 "self": "/api/species?page=1",
14 "next": "/api/species?page=2",
15 "last": "/api/species?page=3"
16 }
17}1{
2 "error": {
3 "code": "SPECIES_NOT_FOUND",
4 "message": "Gatunek 'unicorn' nie istnieje",
5 "status": 404,
6 "timestamp": "2024-01-15T14:30:00Z"
7 }
8}1{
2 "error": {
3 "code": "VALIDATION_ERROR",
4 "message": "Błędy walidacji danych",
5 "status": 400,
6 "details": [
7 {"field": "population", "message": "Musi być liczbą większą od 0"},
8 {"field": "habitat", "message": "Pole wymagane"}
9 ]
10 }
11}Location)1GET /api/species
2X-API-Key: your_api_key_here1GET /api/species
2Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...1GET /api/species
2Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=Najczęściej: Bearer Token (OAuth 2.0, JWT)
1import requests
2from typing import List, Dict, Optional, Any
3from datetime import datetime
4
5class SafariRESTClient:
6 """REST API klient dla Safari Data API"""
7
8 def __init__(self, base_url: str, api_key: str):
9 self.base_url = base_url.rstrip("/")
10 self.session = requests.Session()
11 self.session.headers.update({
12 "Authorization": f"Bearer {api_key}",
13 "Accept": "application/json",
14 "Content-Type": "application/json"
15 })
16
17 # === SPECIES - /api/species ===
18
19 def list_species(self, habitat: Optional[str] = None,
20 endangered: Optional[bool] = None,
21 page: int = 1, per_page: int = 20,
22 sort: str = "name") -> Dict[str, Any]:
23 """
24 GET /api/species
25 Lista gatunków z filtrowaniem, sortowaniem, paginacją
26 """
27 params = {
28 "page": page,
29 "per_page": per_page,
30 "sort": sort
31 }
32 if habitat:
33 params["habitat"] = habitat
34 if endangered is not None:
35 params["endangered"] = str(endangered).lower()
36
37 response = self.session.get(f"{self.base_url}/api/species", params=params)
38 response.raise_for_status()
39 return response.json()
40
41 def get_species(self, species_id: str) -> Dict[str, Any]:
42 """
43 GET /api/species/{id}
44 Pobierz pojedynczy gatunek
45 """
46 response = self.session.get(f"{self.base_url}/api/species/{species_id}")
47 response.raise_for_status()
48 return response.json()
49
50 def create_species(self, data: Dict[str, Any]) -> Dict[str, Any]:
51 """
52 POST /api/species
53 Utwórz nowy gatunek
54 """
55 response = self.session.post(f"{self.base_url}/api/species", json=data)
56 response.raise_for_status()
57
58 if response.status_code == 201:
59 # Zwróć utworzony zasób + ID z Location header
60 location = response.headers.get("Location")
61 print(f"✓ Utworzono: {location}")
62
63 return response.json()
64
65 def update_species(self, species_id: str, data: Dict[str, Any]) -> Dict[str, Any]:
66 """
67 PUT /api/species/{id}
68 Zastąp cały gatunek
69 """
70 response = self.session.put(
71 f"{self.base_url}/api/species/{species_id}",
72 json=data
73 )
74 response.raise_for_status()
75 return response.json()
76
77 def partial_update_species(self, species_id: str,
78 data: Dict[str, Any]) -> Dict[str, Any]:
79 """
80 PATCH /api/species/{id}
81 Zaktualizuj część gatunku
82 """
83 response = self.session.patch(
84 f"{self.base_url}/api/species/{species_id}",
85 json=data
86 )
87 response.raise_for_status()
88 return response.json()
89
90 def delete_species(self, species_id: str) -> bool:
91 """
92 DELETE /api/species/{id}
93 Usuń gatunek
94 """
95 response = self.session.delete(f"{self.base_url}/api/species/{species_id}")
96 response.raise_for_status()
97 return response.status_code == 204
98
99 # === OBSERVATIONS - /api/species/{id}/observations ===
100
101 def list_observations(self, species_id: str,
102 start_date: Optional[str] = None,
103 end_date: Optional[str] = None,
104 page: int = 1) -> Dict[str, Any]:
105 """
106 GET /api/species/{id}/observations
107 Lista obserwacji dla gatunku
108 """
109 params = {"page": page}
110 if start_date:
111 params["start_date"] = start_date
112 if end_date:
113 params["end_date"] = end_date
114
115 response = self.session.get(
116 f"{self.base_url}/api/species/{species_id}/observations",
117 params=params
118 )
119 response.raise_for_status()
120 return response.json()
121
122 def create_observation(self, species_id: str,
123 data: Dict[str, Any]) -> Dict[str, Any]:
124 """
125 POST /api/species/{id}/observations
126 Dodaj obserwację dla gatunku
127 """
128 response = self.session.post(
129 f"{self.base_url}/api/species/{species_id}/observations",
130 json=data
131 )
132 response.raise_for_status()
133 return response.json()
134
135 def get_observation(self, species_id: str, observation_id: str) -> Dict[str, Any]:
136 """
137 GET /api/species/{id}/observations/{obs_id}
138 Pobierz konkretną obserwację
139 """
140 response = self.session.get(
141 f"{self.base_url}/api/species/{species_id}/observations/{observation_id}"
142 )
143 response.raise_for_status()
144 return response.json()
145
146 # === STATISTICS - /api/statistics ===
147
148 def get_statistics(self, group_by: str = "habitat") -> Dict[str, Any]:
149 """
150 GET /api/statistics
151 Pobierz statystyki
152 """
153 params = {"group_by": group_by}
154 response = self.session.get(
155 f"{self.base_url}/api/statistics",
156 params=params
157 )
158 response.raise_for_status()
159 return response.json()
160
161 def close(self):
162 """Zamknij sesję"""
163 self.session.close()
164
165
166# === DEMONSTRACJA ===
167
168print("=== SAFARI REST API CLIENT ===\n")
169
170client = SafariRESTClient(
171 base_url="https://api.safari-data.org",
172 api_key="demo_bearer_token_12345"
173)
174
175# 1. LIST - pobierz wszystkie gatunki zagrożone w sawannie
176print("1. Lista gatunków (filtrowane, sortowane, paginowane)")
177result = client.list_species(
178 habitat="savanna",
179 endangered=True,
180 page=1,
181 per_page=10,
182 sort="-population" # Malejąco według populacji
183)
184
185print(f" Znaleziono: {result['pagination']['total']} gatunków")
186for species in result['data']:
187 print(f" - {species['common_name']}: {species['population']} osobników")
188
189# 2. GET - pobierz konkretny gatunek
190print("\n2. Pobieranie konkretnego gatunku")
191lion = client.get_species("lion")
192print(f" {lion['common_name']} ({lion['scientific_name']})")
193print(f" Populacja: {lion['population']}")
194print(f" Zagrożony: {'Tak' if lion['endangered'] else 'Nie'}")
195
196# 3. POST - utwórz nowy gatunek
197print("\n3. Tworzenie nowego gatunku")
198new_species = {
199 "scientific_name": "Acinonyx jubatus",
200 "common_name": "Gepard",
201 "population": 7100,
202 "habitat": "sawanna",
203 "endangered": True
204}
205
206created = client.create_species(new_species)
207print(f" ✓ Utworzono: {created['common_name']} (ID: {created['id']})")
208
209# 4. PATCH - zaktualizuj tylko populację
210print("\n4. Częściowa aktualizacja (PATCH)")
211updated = client.partial_update_species("lion", {"population": 125})
212print(f" ✓ Zaktualizowano populację: {updated['population']}")
213
214# 5. Obserwacje zagnieżdżone - lista
215print("\n5. Lista obserwacji dla lwa")
216observations = client.list_observations(
217 "lion",
218 start_date="2024-01-01",
219 end_date="2024-01-31"
220)
221
222print(f" Znaleziono {len(observations['data'])} obserwacji w styczniu")
223for obs in observations['data']:
224 print(f" - {obs['date']}: {obs['count']} osobników w {obs['location']}")
225
226# 6. Obserwacje zagnieżdżone - tworzenie
227print("\n6. Dodawanie nowej obserwacji")
228new_obs = {
229 "date": datetime.now().isoformat(),
230 "location": "Serengeti North",
231 "count": 15,
232 "notes": "Pride with 3 cubs"
233}
234
235obs_created = client.create_observation("lion", new_obs)
236print(f" ✓ Utworzono obserwację ID: {obs_created['id']}")
237
238# 7. Statystyki
239print("\n7. Pobieranie statystyk")
240stats = client.get_statistics(group_by="habitat")
241print(" Statystyki według siedliska:")
242for habitat_stat in stats['data']:
243 print(f" - {habitat_stat['habitat']}: {habitat_stat['species_count']} gatunków")
244
245# 8. DELETE - usuń gatunek
246print("\n8. Usuwanie gatunku")
247if client.delete_species("test-species"):
248 print(" ✓ Gatunek usunięty")
249
250client.close()
251print("\n✓ Demonstracja zakończona")✅
/api/species, /api/observations
❌ /api/specie, /api/observation✅
/api/v1/species
❌ /api/species (co gdy zmienisz format?)✅
/species/lion/observations
❌ /continents/africa/countries/kenya/parks/serengeti/species/lion/observations✅
{"scientific_name": "..."} lub {"scientificName": "..."}
❌ {"scientific_name": "...", "commonName": "..."} - mieszane!✅
GET /species?habitat=savanna&sort=-population&page=2
❌ POST /species/filter z body1POST /api/species
2Response: 201 Created
3Location: /api/species/cheetah1Response Headers:
2X-RateLimit-Limit: 1000
3X-RateLimit-Remaining: 999
4X-RateLimit-Reset: 1640000000W tej lekcji nauczyłeś/aś się:
/api/v1/resources/:idPrzed przejściem dalej:
POST /species a GET /species/{id}/species/{id}/observations)Analogia Safari: REST API to uniwersalne standardy komunikacji między stacjami badawczymi na całym świecie. Dzięki nim biolog z Polski wie, że
GET /api/species/lion zwróci dane lwa - bez względu na to, kto zbudował API! 🌍📡W następnej lekcji Darwin nauczy Cię web scrapingu - jak zbierać dane ze stron internetowych, które nie mają API! 🕷️🌐