feat: add nowcast point alarm page client with SQLite store
Adds point_alarm.py (flattening, secret masking, SQLite upsert store, paged fetch with 403 fallback, CSV/TXT export, shared filter loader), nowcast-point-page.py CLI and unit tests. Register will read the shared filter from nowcast-point-alarm-filter.json instead of repeating it in the CSV.
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
"""List point alarm registrations page by page into SQLite, screen or CSV/TXT."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from common import ApiError, ConfigurationError, IklimClient, configure_logging
|
||||
from point_alarm import (
|
||||
MAX_PAGE_SIZE,
|
||||
PointAlarmService,
|
||||
PointStore,
|
||||
fetch_pages,
|
||||
parse_polygon,
|
||||
print_records,
|
||||
prompt_export,
|
||||
timestamped_path,
|
||||
write_records,
|
||||
)
|
||||
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"iklim.co nokta alarm kayıtlarını /v1/alarms/points/page ile listeler. "
|
||||
"Sonuçlar SQLite'a (registrationId anahtarlı) yazılır; varsayılan olarak ekrana "
|
||||
"basılır, --output ile CSV/TXT'ye yazılır."
|
||||
),
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--account-id",
|
||||
help="Sorgulanacak hesap ID'si (varsayılan: /v1/users/me içindeki account.id)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--recipient-id",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Alıcı ID'sine göre süz; birden fazla kez verilebilir",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--polygon",
|
||||
help="Poligon kesişim filtresi: 'lng,lat;lng,lat;...' (en az 4 nokta, kapalı halka)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--page-number",
|
||||
type=int,
|
||||
help="Yalnızca bu sayfayı getir (0'dan başlar); verilmezse tüm sayfalar gezilir",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--page-size",
|
||||
type=int,
|
||||
default=MAX_PAGE_SIZE,
|
||||
help=f"Sayfa boyutu, 1-{MAX_PAGE_SIZE} (varsayılan: {MAX_PAGE_SIZE})",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
type=Path,
|
||||
help=(
|
||||
"Çıktı dosyası (.csv noktalı virgüllü, .txt JSON Lines); dosya adına "
|
||||
"zaman soneki eklenir (dosya-YYYYmmdd-HHMMSS.csv). Verilmezse ekrana yazılır"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--db",
|
||||
type=Path,
|
||||
default=BASE_DIR / "iklim-sandbox.db",
|
||||
help="SQLite dosyası (varsayılan: iklim-sandbox.db)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--debug-log",
|
||||
type=Path,
|
||||
default=BASE_DIR / "logs" / "iklim-api-debug.log",
|
||||
help="Maskelenmiş HTTP debug log dosyası",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
if not 1 <= args.page_size <= MAX_PAGE_SIZE:
|
||||
parser.error(f"--page-size 1-{MAX_PAGE_SIZE} aralığında olmalıdır")
|
||||
if args.page_number is not None and args.page_number < 0:
|
||||
parser.error("--page-number 0 veya daha büyük olmalıdır")
|
||||
return args
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
configure_logging(args.debug_log)
|
||||
store = None
|
||||
try:
|
||||
polygon = parse_polygon(args.polygon) if args.polygon else None
|
||||
client = IklimClient.from_env(BASE_DIR / ".env")
|
||||
service = PointAlarmService(client)
|
||||
account_id = args.account_id or service.get_account_id()
|
||||
store = PointStore(args.db)
|
||||
summary = fetch_pages(
|
||||
service,
|
||||
store,
|
||||
account_id=account_id,
|
||||
recipient_ids=args.recipient_id or None,
|
||||
polygon=polygon,
|
||||
page_size=args.page_size,
|
||||
page_number=args.page_number,
|
||||
)
|
||||
records = store.records(summary.account_id, summary.registration_ids)
|
||||
webhooks = store.webhooks(summary.account_id)
|
||||
if args.output:
|
||||
path = timestamped_path(args.output)
|
||||
write_records(path, records, webhooks)
|
||||
print(f"{len(records)} kayıt yazıldı: {path.resolve()}")
|
||||
else:
|
||||
print_records(records, webhooks)
|
||||
if args.page_number is None and summary.missing:
|
||||
logging.warning(
|
||||
"Sunucu %s kayıt bildirdi, %s benzersiz kayıt alındı (%s eksik). Sayfalama sırasında "
|
||||
"kayıt eklenmiş/silinmiş olabilir; komutu yeniden çalıştırın.",
|
||||
summary.total_records,
|
||||
summary.distinct,
|
||||
summary.missing,
|
||||
)
|
||||
if summary.duplicates:
|
||||
logging.warning("Sayfalar arasında %s tekrar eden kayıt görüldü.", summary.duplicates)
|
||||
if not args.output:
|
||||
prompt_export(store, summary.account_id)
|
||||
except (ConfigurationError, ApiError, OSError) as exc:
|
||||
logging.error("İşlem durduruldu: %s", exc)
|
||||
return 1
|
||||
finally:
|
||||
if store:
|
||||
store.close()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user