Кэширование и мониторинг
Семантика пустых списков и нулевого числа оповещений: Обработка ошибок. HTTP-ошибки платформы и повторные попытки: Обработка ошибок.
Актуальность данных
| Эндпоинт | Частота обновления | Рекомендуемый TTL | Примечания |
|---|---|---|---|
GET /notams/{icao} | Непрерывно | не более 5 мин | NOTAM выпускаются, изменяются и отменяются в любой момент |
GET /delays/faa | Каждые 2–5 мин (ATCSCC) | 2–5 мин | Снимок по всей стране; запреты на вылет (ground stop) могут появляться без предварительного уведомления |
GET /delays/faa/{icao} | Каждые 2–5 мин (ATCSCC) | 2–5 мин | Срез того же потока по конкретному аэропорту |
Не кэшируйте дольше рекомендуемого TTL ни для одного из эндпоинтов. Если запрет на вылет введён, пока из кэша отдаётся устаревшая запись, ваши пользователи увидят «нет действующих задержек» во время действующей программы.
Кэширование
import os
import time
import requests
HEADERS = {
"X-RapidAPI-Key": os.getenv("RAPIDAPI_KEY", "YOUR_RAPIDAPI_KEY"),
"X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}
BASE = "https://skylink-api.p.rapidapi.com"
_cache: dict[str, tuple[dict, float]] = {}
NOTAM_TTL = 300 # 5 minutes
FAA_DELAY_TTL = 120 # 2 minutes
def cached_get(url: str, ttl_seconds: int) -> dict | None:
entry = _cache.get(url)
if entry and time.time() < entry[1]:
return entry[0]
r = requests.get(url, headers=HEADERS, timeout=(10, 15))
if r.status_code == 404:
return None
r.raise_for_status()
data = r.json()
_cache[url] = (data, time.time() + ttl_seconds)
return data
def get_notams(icao: str) -> dict | None:
"""Cached NOTAM fetch — 5-minute TTL."""
return cached_get(f"{BASE}/notams/{icao}", NOTAM_TTL)
def get_faa_delays_nationwide() -> dict | None:
"""Cached nationwide FAA delay snapshot — 2-minute TTL."""
return cached_get(f"{BASE}/delays/faa", FAA_DELAY_TTL)
# data = get_notams("KJFK")
# delays = get_faa_delays_nationwide()В продакшне замените кэш-словарь на общее хранилище (Redis, Memcached), чтобы кэш был согласован между экземплярами и переживал перезапуски.
Ограничения частоты запросов
Каждый ответ содержит заголовки ограничения частоты запросов:
| Заголовок | Значение |
|---|---|
X-RateLimit-Requests-Limit | Месячная квота запросов вашего тарифа |
X-RateLimit-Requests-Remaining | Сколько запросов осталось в этом месяце |
X-RateLimit-Requests-Reset | Секунд до сброса квоты |
Квоты тарифов:
| Тариф | Запросов в месяц |
|---|---|
| Free | 1000 |
| Basic | 5000 |
| Pro | 50 000 |
| Ultra | 200 000 |
| Mega | 600 000 |
Мониторинг и оповещения
Записывайте в лог каждый запрос NOTAM и задержек FAA и настройте активные оповещения об эксплуатационно значимых изменениях:
import logging
import time
import requests
logger = logging.getLogger("alerts")
HEADERS = {
"X-RapidAPI-Key": os.getenv("RAPIDAPI_KEY", "YOUR_RAPIDAPI_KEY"),
"X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}
BASE = "https://skylink-api.p.rapidapi.com"
# Track previous counts to detect sudden changes
_prev_notam_counts: dict[str, int] = {}
_prev_faa_ground_stops: set[str] = set()
WATCHED_AIRPORTS = ["KJFK", "KLAX", "KORD"]
def check_notams_for_airport(icao: str) -> None:
start = time.monotonic()
r = requests.get(f"{BASE}/notams/{icao}", headers=HEADERS, timeout=(10, 15))
elapsed_ms = (time.monotonic() - start) * 1000
log_data = {
"endpoint": "notams",
"icao": icao,
"status": r.status_code,
"elapsed_ms": round(elapsed_ms),
"quota_remaining": r.headers.get("X-RateLimit-Requests-Remaining"),
}
if r.status_code == 200:
data = r.json()
count = data.get("total", len(data.get("notams", [])))
log_data["notam_count"] = count
logger.info("notam_fetch", extra=log_data)
prev = _prev_notam_counts.get(icao)
if prev is not None and abs(count - prev) >= 5:
logger.warning(
"notam_count_jump",
extra={"icao": icao, "prev": prev, "current": count},
)
_prev_notam_counts[icao] = count
elif r.status_code == 404:
logger.info("notam_not_found", extra=log_data)
else:
logger.warning("notam_error", extra=log_data)
r.raise_for_status()
def check_faa_ground_stops() -> None:
start = time.monotonic()
r = requests.get(f"{BASE}/delays/faa", headers=HEADERS, timeout=(10, 15))
elapsed_ms = (time.monotonic() - start) * 1000
log_data = {
"endpoint": "delays/faa",
"status": r.status_code,
"elapsed_ms": round(elapsed_ms),
"quota_remaining": r.headers.get("X-RateLimit-Requests-Remaining"),
}
if r.status_code == 200:
data = r.json()
total = data.get("total_alerts", 0)
log_data["total_alerts"] = total
logger.info("faa_delay_fetch", extra=log_data)
# Alert when a ground stop appears at a watched airport
current_gs = {
gs["airport"]
for gs in data.get("ground_stops", [])
if gs.get("airport") in WATCHED_AIRPORTS
}
new_gs = current_gs - _prev_faa_ground_stops
if new_gs:
logger.warning(
"ground_stop_appeared",
extra={"airports": sorted(new_gs)},
)
_prev_faa_ground_stops.clear()
_prev_faa_ground_stops.update(current_gs)
else:
logger.warning("faa_delay_error", extra=log_data)
r.raise_for_status()
# for icao in WATCHED_AIRPORTS:
# check_notams_for_airport(icao)
# check_faa_ground_stops()Настройте оповещения на:
notam_count_jump— число NOTAM в отслеживаемом аэропорту изменилось на 5 или более между опросами; необычный всплеск может указывать на ограничения воздушного пространства или экстренные уведомленияground_stop_appeared— в любом аэропорту из вашего списка отслеживания введён запрет на вылет (ground stop); такие запреты могут остановить все вылеты с уведомлением всего за несколько минутelapsed_ms > 5000повторно — проблема с задержками на стороне внешнего сервиса; предусмотрите тайм-аут и резервный интерфейсquota_remaining < 200— вы близки к месячному лимиту; уменьшите частоту опроса или перейдите на более высокий тарифstatus == 429более двух раз за 5 минут — частота опроса слишком высока
Готовый к запуску пример
import os
import time
import requests
HEADERS = {
"X-RapidAPI-Key": os.getenv("RAPIDAPI_KEY", "YOUR_RAPIDAPI_KEY"),
"X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}
BASE = "https://skylink-api.p.rapidapi.com"
_cache: dict[str, tuple[list, float]] = {}
def get_notams(icao: str) -> list[dict]:
key = icao.upper()
hit = _cache.get(key)
if hit and time.time() < hit[1]:
return hit[0]
r = requests.get(f"{BASE}/notams/{key}", headers=HEADERS, timeout=(10, 15))
r.raise_for_status()
rows = r.json().get("notams") or []
_cache[key] = (rows, time.time() + 300)
return rows
if __name__ == "__main__":
rows = get_notams("KJFK")
print(f"KJFK NOTAMs: {len(rows)} (first call)")
print(f"Cached repeat: {len(get_notams('KJFK'))} NOTAMs")См. также: NOTAM · Задержки FAA · Обработка ошибок