get_json поменял местами данные
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import time
|
import time
|
||||||
import os
|
import os, tempfile
|
||||||
import json
|
import json
|
||||||
import tempfile
|
import tempfile
|
||||||
import argparse
|
import argparse
|
||||||
@@ -9,6 +9,7 @@ import logging.config
|
|||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
from typing import Any, Dict, List, Tuple, Optional
|
from typing import Any, Dict, List, Tuple, Optional
|
||||||
|
from pathlib import Path
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
|
|||||||
# 1. Константы / глобальные объекты
|
# 1. Константы / глобальные объекты
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
HOST = "https://ref.russiabasket.org"
|
HOST = "https://deti.russiabasket.org"
|
||||||
|
|
||||||
# Таймзона, в которой мы считаем время матчей / расписания / сна до завтра
|
# Таймзона, в которой мы считаем время матчей / расписания / сна до завтра
|
||||||
APP_TZ = ZoneInfo("Europe/Moscow")
|
APP_TZ = ZoneInfo("Europe/Moscow")
|
||||||
@@ -35,7 +36,8 @@ TELEGRAM_BOT_TOKEN = "7639240596:AAH0YtdQoWZSC-_R_EW4wKAHHNLIA0F_ARY"
|
|||||||
TELEGRAM_CHAT_ID = -4803699526
|
TELEGRAM_CHAT_ID = -4803699526
|
||||||
|
|
||||||
# Глобальный лок для потокобезопасной записи JSON
|
# Глобальный лок для потокобезопасной записи JSON
|
||||||
_write_lock = threading.Lock()
|
_write_lock_api = threading.Lock()
|
||||||
|
_write_lock_out = threading.Lock()
|
||||||
_pregame_done_for_game = {}
|
_pregame_done_for_game = {}
|
||||||
|
|
||||||
# Карта всех ручек API, с интервалами опроса в секундах.
|
# Карта всех ручек API, с интервалами опроса в секундах.
|
||||||
@@ -146,28 +148,41 @@ logger.handlers[2].formatter.use_emoji = True
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
def atomic_write_json(data: Any, name: str, out_dir: str = "static") -> None:
|
def _select_lock(path: str):
|
||||||
|
filename = os.path.basename(path)
|
||||||
|
# все сырые файлы мы называем api_*.json (api_game.json, api_box-score.json, ...)
|
||||||
|
if filename.startswith("api_"):
|
||||||
|
return _write_lock_api
|
||||||
|
return _write_lock_out
|
||||||
|
|
||||||
|
def atomic_write_json(path: str, data: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Потокобезопасная запись JSON в static/<name>.json.
|
Безопасно записывает JSON:
|
||||||
|
1. Сериализуем в память (без локов).
|
||||||
Запись делается через временный файл + os.replace, чтобы:
|
2. Под коротким локом - пишем tmp и делаем os.replace().
|
||||||
- читатели не получили битый файл во время перезаписи;
|
|
||||||
- не было гонок между render_loop и poll_game_live.
|
|
||||||
"""
|
"""
|
||||||
os.makedirs(out_dir, exist_ok=True)
|
# 1. Готовим данные заранее
|
||||||
filename = os.path.join(out_dir, f"{name}.json")
|
# ensure_ascii=False чтобы не терять юникод, indent=None чтобы не раздувать файл
|
||||||
|
payload = json.dumps(data, ensure_ascii=False, separators=(",", ":"))
|
||||||
|
|
||||||
with _write_lock:
|
target = Path(path)
|
||||||
with tempfile.NamedTemporaryFile(
|
tmp_fd, tmp_path = tempfile.mkstemp(
|
||||||
"w", delete=False, dir=out_dir, encoding="utf-8"
|
dir=target.parent,
|
||||||
) as tmp_file:
|
prefix=target.name + ".tmp.",
|
||||||
json.dump(data, tmp_file, ensure_ascii=False, indent=2)
|
text=True,
|
||||||
tmp_file.flush()
|
)
|
||||||
os.fsync(tmp_file.fileno())
|
os.close(tmp_fd) # мы будем писать сами
|
||||||
tmp_name = tmp_file.name
|
|
||||||
|
|
||||||
os.replace(tmp_name, filename)
|
lock = _select_lock(path)
|
||||||
|
with lock:
|
||||||
|
# 2a. Записываем полностью во временный файл
|
||||||
|
with open(tmp_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(payload)
|
||||||
|
f.flush()
|
||||||
|
os.fsync(f.fileno())
|
||||||
|
|
||||||
|
# 2b. Атомарно подменяем
|
||||||
|
os.replace(tmp_path, target)
|
||||||
|
|
||||||
def read_local_json(name: str, in_dir: str = "static") -> Optional[dict]:
|
def read_local_json(name: str, in_dir: str = "static") -> Optional[dict]:
|
||||||
"""
|
"""
|
||||||
@@ -251,7 +266,7 @@ def get_json(session: requests.Session, url: str, name: str) -> Any:
|
|||||||
resp = session.get(url, timeout=10)
|
resp = session.get(url, timeout=10)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
atomic_write_json(data, f"api_{name}")
|
atomic_write_json(f"api_{name}", data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user