vivekchakraverty commited on
Commit
4cf533e
·
verified ·
1 Parent(s): e6ffeb8

Enforce monthly request cap (12000/mo plan) via RapidAPI quota headers; optional MONTHLY_DOWNLOAD_LIMIT self-cap; show quota in health check

Browse files
Files changed (1) hide show
  1. pipeline/downloader.py +64 -1
pipeline/downloader.py CHANGED
@@ -19,11 +19,68 @@ import requests
19
  RAPIDAPI_HOST = "youtube-video-fast-downloader-24-7.p.rapidapi.com"
20
  DEFAULT_QUALITY = "18" # itag 18 = 360p muxed (has audio, ~10-30 MB for typical videos)
21
 
 
 
 
 
 
22
 
23
  class DownloadError(RuntimeError):
24
  """Raised when the video can't be obtained from the download API."""
25
 
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  def _headers() -> dict:
28
  key = os.environ.get("RAPIDAPI_KEY", "").strip()
29
  if not key:
@@ -38,6 +95,10 @@ def _request_urls(video_id: str, quality: str, timeout: int = 60) -> tuple[list[
38
  r = requests.get(url, params={"quality": quality}, headers=_headers(), timeout=timeout)
39
  except requests.RequestException as exc:
40
  raise DownloadError(f"download API unreachable: {exc}") from exc
 
 
 
 
41
  if r.status_code in (401, 403):
42
  raise DownloadError(f"download API auth failed (HTTP {r.status_code}); "
43
  "check RAPIDAPI_KEY / that you're subscribed.")
@@ -56,8 +117,10 @@ def download_video(video_id: str, dest: str, quality: str = DEFAULT_QUALITY,
56
  """Download ``video_id`` to ``dest`` and return the path.
57
 
58
  Polls the (async) provider link until ready (404 -> wait), then streams it to ``dest``.
59
- Raises DownloadError if it never becomes ready within ``poll_timeout`` seconds.
 
60
  """
 
61
  urls, _ = _request_urls(video_id, quality)
62
  deadline = time.time() + poll_timeout
63
  last = "not ready"
 
19
  RAPIDAPI_HOST = "youtube-video-fast-downloader-24-7.p.rapidapi.com"
20
  DEFAULT_QUALITY = "18" # itag 18 = 360p muxed (has audio, ~10-30 MB for typical videos)
21
 
22
+ # RapidAPI tracks monthly usage against the plan limit and returns it in every response
23
+ # header. We mirror the latest values here and enforce the cap — no persistence needed,
24
+ # and it's Space-wide + monthly (the plan's billing cycle) by construction.
25
+ _quota = {"limit": None, "remaining": None, "reset": None} # reset = seconds until reset
26
+
27
 
28
  class DownloadError(RuntimeError):
29
  """Raised when the video can't be obtained from the download API."""
30
 
31
 
32
+ def _update_quota(headers) -> None:
33
+ def _int(name):
34
+ try:
35
+ return int(headers.get(name))
36
+ except (TypeError, ValueError):
37
+ return None
38
+ for key, hdr in (("limit", "X-RateLimit-Requests-Limit"),
39
+ ("remaining", "X-RateLimit-Requests-Remaining"),
40
+ ("reset", "X-RateLimit-Requests-Reset")):
41
+ val = _int(hdr)
42
+ if val is not None:
43
+ _quota[key] = val
44
+
45
+
46
+ def quota_status() -> dict:
47
+ """Latest known monthly quota: ``{limit, remaining, reset(sec), used, self_cap}``."""
48
+ q = dict(_quota)
49
+ q["used"] = (q["limit"] - q["remaining"]) if (q["limit"] and q["remaining"] is not None) else None
50
+ q["self_cap"] = _self_cap()
51
+ return q
52
+
53
+
54
+ def _reset_days() -> str:
55
+ r = _quota.get("reset")
56
+ return f"~{max(1, r // 86400)} day(s)" if r else "the next cycle"
57
+
58
+
59
+ def _self_cap() -> int | None:
60
+ """Optional lower monthly cap (env ``MONTHLY_DOWNLOAD_LIMIT``); None = use plan limit."""
61
+ v = os.environ.get("MONTHLY_DOWNLOAD_LIMIT", "").strip()
62
+ try:
63
+ return int(v) if v else None
64
+ except ValueError:
65
+ return None
66
+
67
+
68
+ def _enforce_quota() -> None:
69
+ """Refuse before spending a request if the monthly cap is already reached."""
70
+ q = _quota
71
+ if q["remaining"] is None:
72
+ return # unknown yet (fresh start) — let the call itself surface a 429
73
+ if q["limit"] is not None:
74
+ used = q["limit"] - q["remaining"]
75
+ cap = _self_cap()
76
+ if cap is not None and used >= cap:
77
+ raise DownloadError(
78
+ f"Self-imposed monthly cap reached: {used} of {cap} used. Resets in {_reset_days()}.")
79
+ if q["remaining"] <= 0:
80
+ raise DownloadError(
81
+ f"Monthly request limit reached (plan: {q['limit']}). Resets in {_reset_days()}.")
82
+
83
+
84
  def _headers() -> dict:
85
  key = os.environ.get("RAPIDAPI_KEY", "").strip()
86
  if not key:
 
95
  r = requests.get(url, params={"quality": quality}, headers=_headers(), timeout=timeout)
96
  except requests.RequestException as exc:
97
  raise DownloadError(f"download API unreachable: {exc}") from exc
98
+ _update_quota(r.headers)
99
+ if r.status_code == 429:
100
+ raise DownloadError(f"Monthly request limit reached (plan: {_quota.get('limit')}). "
101
+ f"Resets in {_reset_days()}.")
102
  if r.status_code in (401, 403):
103
  raise DownloadError(f"download API auth failed (HTTP {r.status_code}); "
104
  "check RAPIDAPI_KEY / that you're subscribed.")
 
117
  """Download ``video_id`` to ``dest`` and return the path.
118
 
119
  Polls the (async) provider link until ready (404 -> wait), then streams it to ``dest``.
120
+ Raises DownloadError if it never becomes ready within ``poll_timeout`` seconds, or if
121
+ the monthly request cap is already reached.
122
  """
123
+ _enforce_quota() # refuse before spending a request if the monthly cap is reached
124
  urls, _ = _request_urls(video_id, quality)
125
  deadline = time.time() + poll_timeout
126
  last = "not ready"