""" Клиенты и API-ключи: python -m db.cli create # выпустить ключ: печатается один раз python -m db.cli list python -m db.cli revoke python -m db.cli stats [--days 7] # запросы, их время и время работы браузера по клиентам В compose — через make: make key-create name=..., make key-list, make key-revoke id=..., make stats days=... """ import argparse import asyncio from db import clients, stats from db.engine import connect async def run(args) -> None: db = connect() try: await db.init() if args.command == "create": client, key = await clients.create_client(db, args.name) print(f"client {client.id} ({client.name}), API key — сохраните, больше он не показывается:\n{key}") elif args.command == "list": for c in await clients.list_clients(db): state = f"revoked {c.revoked_at:%Y-%m-%d %H:%M}" if c.revoked_at else "active" last = f"{c.last_access_at:%Y-%m-%d %H:%M}" if c.last_access_at else "never" print(f"{c.id:>4} {c.key_prefix}… {state:<24} last access {last:<16} {c.name}") elif args.command == "revoke": print("revoked" if await clients.revoke(db, args.id) else "no active client with this id") elif args.command == "stats": print_stats(await stats.usage(db, stats.since_days(args.days)), args.days) finally: await db.close() def _ms(value: int | None) -> str: return "—" if value is None else f"{value / 1000:.1f}s" def print_stats(rows: list, days: float) -> None: print(f"за {days:g} дн.") print(f"{'клиент':<24} {'запросов':>8} {'ok':>6} {'ср.':>7} {'p95':>7} {'окон':>5} {'браузер, ч':>10} ошибки") for u in rows: errors = ", ".join(f"{k} {v}" for k, v in sorted(u.errors.items())) or "—" name = f"{u.client_id or '-'} {u.name}"[:24] print( f"{name:<24} {u.requests:>8} {u.ok:>6} {_ms(u.avg_ms):>7} {_ms(u.p95_ms):>7} " f"{u.windows:>5} {u.browser_seconds / 3600:>10.2f} {errors}" ) if not rows: print("запросов не было") def main() -> None: parser = argparse.ArgumentParser(prog="python -m db.cli", description="Клиенты Robotex и их API-ключи") sub = parser.add_subparsers(dest="command", required=True) sub.add_parser("create", help="выпустить ключ новому клиенту").add_argument("name") sub.add_parser("list", help="список клиентов") sub.add_parser("revoke", help="отозвать ключ").add_argument("id", type=int) sub.add_parser("stats", help="статистика использования по клиентам").add_argument("--days", type=float, default=7) asyncio.run(run(parser.parse_args())) if __name__ == "__main__": main()