-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbeatapi_client.py
More file actions
130 lines (111 loc) · 3.84 KB
/
Copy pathbeatapi_client.py
File metadata and controls
130 lines (111 loc) · 3.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
from __future__ import annotations
from collections.abc import Callable, Mapping
from typing import Any
from urllib.parse import quote
RequestFn = Callable[..., Any]
class BeatAPIError(RuntimeError):
def __init__(
self,
message: str,
*,
code: str = "beatapi_request_failed",
request_id: str | None = None,
status_code: int | None = None,
) -> None:
super().__init__(message)
self.code = code
self.request_id = request_id
self.status_code = status_code
def _default_request(**kwargs: Any) -> Any:
import requests
return requests.request(**kwargs)
class BeatAPIClient:
"""Small public interface for authenticated BeatAPI workflow requests."""
def __init__(
self,
api_key: str,
*,
base_url: str = "https://api.beatapi.io",
timeout: int = 30,
request: RequestFn | None = None,
) -> None:
normalized_key = api_key.strip()
if not normalized_key:
raise ValueError("A BeatAPI API key is required.")
self._api_key = normalized_key
self._base_url = base_url.rstrip("/")
self._timeout = timeout
self._request_fn = request or _default_request
def get_usage(self) -> dict[str, Any]:
return self._request("GET", "/v1/usage")
def create_music_video_task(
self,
*,
images: list[str],
audio_url: str,
**options: Any,
) -> dict[str, Any]:
payload = {
"images": images,
"audio_url": audio_url,
**options,
}
compact_payload = {
key: value
for key, value in payload.items()
if value is not None and value != "" and value != []
}
return self._request(
"POST",
"/v1/music-video/tasks",
json=compact_payload,
)
def get_task(self, task_id: str) -> dict[str, Any]:
normalized_task_id = task_id.strip()
if not normalized_task_id:
raise ValueError("A BeatAPI task ID is required.")
return self._request("GET", f"/v1/tasks/{quote(normalized_task_id, safe='')}")
def _request(
self,
method: str,
path: str,
*,
json: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
response = self._request_fn(
method=method,
url=f"{self._base_url}{path}",
headers={
"Authorization": f"Bearer {self._api_key}",
"Accept": "application/json",
"Content-Type": "application/json",
"User-Agent": "BeatAPI-Dify-Plugin/0.1.0",
},
json=json,
timeout=self._timeout,
)
try:
payload = response.json()
except Exception as exc:
raise BeatAPIError(
"BeatAPI returned a non-JSON response.",
status_code=getattr(response, "status_code", None),
) from exc
status_code = getattr(response, "status_code", None)
if status_code is None or status_code < 200 or status_code >= 300:
error = payload.get("error", {}) if isinstance(payload, dict) else {}
message = error.get("message") or "BeatAPI request failed."
raise BeatAPIError(
message,
code=error.get("code") or "beatapi_request_failed",
request_id=error.get("request_id"),
status_code=status_code,
)
data = payload.get("data") if isinstance(payload, dict) else None
if not isinstance(data, dict):
raise BeatAPIError(
"BeatAPI returned an unexpected response shape.",
code="invalid_beatapi_response",
status_code=status_code,
)
return data