diffio 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diffio/__init__.py +59 -0
- diffio/client.py +1460 -0
- diffio/errors.py +15 -0
- diffio/testing.py +296 -0
- diffio/types.py +333 -0
- diffio-0.1.0.dist-info/METADATA +234 -0
- diffio-0.1.0.dist-info/RECORD +10 -0
- diffio-0.1.0.dist-info/WHEEL +5 -0
- diffio-0.1.0.dist-info/licenses/LICENSE +21 -0
- diffio-0.1.0.dist-info/top_level.txt +1 -0
diffio/errors.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class DiffioApiError(Exception):
|
|
2
|
+
def __init__(self, message, statusCode=None, responseBody=None):
|
|
3
|
+
super().__init__(message)
|
|
4
|
+
self.message = message
|
|
5
|
+
self.statusCode = statusCode
|
|
6
|
+
self.responseBody = responseBody
|
|
7
|
+
|
|
8
|
+
def __str__(self):
|
|
9
|
+
details = []
|
|
10
|
+
if self.statusCode is not None:
|
|
11
|
+
details.append(f"statusCode={self.statusCode}")
|
|
12
|
+
if self.responseBody is not None:
|
|
13
|
+
details.append("responseBody set")
|
|
14
|
+
suffix = f" ({', '.join(details)})" if details else ""
|
|
15
|
+
return f"{self.message}{suffix}"
|
diffio/testing.py
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import secrets
|
|
5
|
+
from urllib.parse import urlparse
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
ALLOWED_LOCAL_HOSTS = {"localhost", "127.0.0.1", "0.0.0.0", "::1"}
|
|
10
|
+
DEFAULT_PROJECT_ID = "diffioai"
|
|
11
|
+
DEFAULT_AUTH_HOST = "127.0.0.1:9099"
|
|
12
|
+
DEFAULT_FUNCTIONS_HOST = "127.0.0.1:5001"
|
|
13
|
+
DEFAULT_WEB_API_KEY = "fake-api-key"
|
|
14
|
+
DEFAULT_REGION = "us-central1"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class EmulatorApiKeyResult:
|
|
18
|
+
def __init__(self, api_key, key_id, key_prefix, label, user_id, email, password, id_token):
|
|
19
|
+
self.api_key = api_key
|
|
20
|
+
self.key_id = key_id
|
|
21
|
+
self.key_prefix = key_prefix
|
|
22
|
+
self.label = label
|
|
23
|
+
self.user_id = user_id
|
|
24
|
+
self.email = email
|
|
25
|
+
self.password = password
|
|
26
|
+
self.id_token = id_token
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class EmulatorApiKeyError(RuntimeError):
|
|
30
|
+
def __init__(self, message, status_code=None, payload=None, code=None):
|
|
31
|
+
super().__init__(message)
|
|
32
|
+
self.status_code = status_code
|
|
33
|
+
self.payload = payload
|
|
34
|
+
self.code = code
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _normalize_origin(raw_host, default_port, label):
|
|
38
|
+
host = (raw_host or "").strip()
|
|
39
|
+
if not host:
|
|
40
|
+
raise ValueError(f"Missing {label} host.")
|
|
41
|
+
if not host.startswith(("http://", "https://")):
|
|
42
|
+
host = f"http://{host}"
|
|
43
|
+
parsed = urlparse(host)
|
|
44
|
+
hostname = parsed.hostname
|
|
45
|
+
if not hostname:
|
|
46
|
+
raise ValueError(f"Invalid {label} host {raw_host}.")
|
|
47
|
+
if hostname not in ALLOWED_LOCAL_HOSTS:
|
|
48
|
+
raise ValueError(
|
|
49
|
+
f"Refusing to contact non-local {label} host {hostname}. Set the emulator host to localhost."
|
|
50
|
+
)
|
|
51
|
+
port = parsed.port or default_port
|
|
52
|
+
origin = f"{parsed.scheme}://{hostname}:{port}"
|
|
53
|
+
return origin, hostname
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _parse_error(payload):
|
|
57
|
+
if not isinstance(payload, dict):
|
|
58
|
+
return None, None
|
|
59
|
+
error = payload.get("error")
|
|
60
|
+
if not isinstance(error, dict):
|
|
61
|
+
return None, None
|
|
62
|
+
message = error.get("message") or error.get("status")
|
|
63
|
+
return message, error
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _request_json(
|
|
67
|
+
client,
|
|
68
|
+
*,
|
|
69
|
+
method,
|
|
70
|
+
url,
|
|
71
|
+
headers=None,
|
|
72
|
+
json_body=None,
|
|
73
|
+
):
|
|
74
|
+
response = client.request(method, url, headers=headers, json=json_body)
|
|
75
|
+
payload = None
|
|
76
|
+
try:
|
|
77
|
+
payload = response.json()
|
|
78
|
+
except ValueError:
|
|
79
|
+
payload = None
|
|
80
|
+
error_code, _ = _parse_error(payload)
|
|
81
|
+
if response.status_code >= 400 or error_code:
|
|
82
|
+
message = error_code or response.text or f"Request failed with {response.status_code}."
|
|
83
|
+
raise EmulatorApiKeyError(message, status_code=response.status_code, payload=payload, code=error_code)
|
|
84
|
+
if not isinstance(payload, dict):
|
|
85
|
+
raise EmulatorApiKeyError("Unexpected response payload.", status_code=response.status_code, payload=payload)
|
|
86
|
+
return payload
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _create_or_sign_in_user(
|
|
90
|
+
client,
|
|
91
|
+
*,
|
|
92
|
+
identity_base,
|
|
93
|
+
email,
|
|
94
|
+
password,
|
|
95
|
+
web_api_key,
|
|
96
|
+
allow_existing,
|
|
97
|
+
):
|
|
98
|
+
signup_url = f"{identity_base}/accounts:signUp?key={web_api_key}"
|
|
99
|
+
payload = {"email": email, "password": password, "returnSecureToken": True}
|
|
100
|
+
try:
|
|
101
|
+
data = _request_json(client, method="POST", url=signup_url, json_body=payload)
|
|
102
|
+
except EmulatorApiKeyError as exc:
|
|
103
|
+
if exc.code == "EMAIL_EXISTS":
|
|
104
|
+
if not allow_existing:
|
|
105
|
+
raise EmulatorApiKeyError(
|
|
106
|
+
"Email already exists. Provide the correct password to sign in.",
|
|
107
|
+
status_code=exc.status_code,
|
|
108
|
+
payload=exc.payload,
|
|
109
|
+
code=exc.code,
|
|
110
|
+
) from exc
|
|
111
|
+
signin_url = f"{identity_base}/accounts:signInWithPassword?key={web_api_key}"
|
|
112
|
+
data = _request_json(client, method="POST", url=signin_url, json_body=payload)
|
|
113
|
+
else:
|
|
114
|
+
raise
|
|
115
|
+
uid = data.get("localId")
|
|
116
|
+
id_token = data.get("idToken")
|
|
117
|
+
if not uid or not id_token:
|
|
118
|
+
raise EmulatorApiKeyError("Auth emulator response missing uid or idToken.", payload=data)
|
|
119
|
+
return uid, id_token
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def create_emulator_api_key(
|
|
123
|
+
*,
|
|
124
|
+
label=None,
|
|
125
|
+
email=None,
|
|
126
|
+
password=None,
|
|
127
|
+
project_id=None,
|
|
128
|
+
auth_emulator_host=None,
|
|
129
|
+
functions_emulator_host=None,
|
|
130
|
+
web_api_key=None,
|
|
131
|
+
region=None,
|
|
132
|
+
is_restricted=False,
|
|
133
|
+
spend_limit=None,
|
|
134
|
+
permissions=None,
|
|
135
|
+
http_client=None,
|
|
136
|
+
):
|
|
137
|
+
"""Create an emulator API key linked to an emulator Auth user."""
|
|
138
|
+
project_id = project_id or os.environ.get("FIREBASE_PROJECT_ID", DEFAULT_PROJECT_ID)
|
|
139
|
+
region = region or DEFAULT_REGION
|
|
140
|
+
auth_host = auth_emulator_host or os.environ.get("FIREBASE_AUTH_EMULATOR_HOST", DEFAULT_AUTH_HOST)
|
|
141
|
+
functions_host = functions_emulator_host or os.environ.get(
|
|
142
|
+
"FUNCTIONS_EMULATOR_HOST",
|
|
143
|
+
os.environ.get("FIREBASE_FUNCTIONS_EMULATOR_HOST", DEFAULT_FUNCTIONS_HOST),
|
|
144
|
+
)
|
|
145
|
+
web_api_key = web_api_key or os.environ.get("FIREBASE_WEB_API_KEY", DEFAULT_WEB_API_KEY)
|
|
146
|
+
|
|
147
|
+
auth_origin, _ = _normalize_origin(auth_host, 9099, "Auth emulator")
|
|
148
|
+
functions_origin, _ = _normalize_origin(functions_host, 5001, "Functions emulator")
|
|
149
|
+
|
|
150
|
+
label = label or f"test-key-{secrets.token_hex(4)}"
|
|
151
|
+
email = email or f"test-{secrets.token_hex(4)}@example.com"
|
|
152
|
+
password_provided = password is not None
|
|
153
|
+
password = password or secrets.token_urlsafe(12)
|
|
154
|
+
|
|
155
|
+
if is_restricted or spend_limit is not None:
|
|
156
|
+
if spend_limit is None:
|
|
157
|
+
raise ValueError("spendLimit is required when restricted.")
|
|
158
|
+
is_restricted = True
|
|
159
|
+
|
|
160
|
+
owns_client = http_client is None
|
|
161
|
+
client = http_client or httpx.Client(timeout=10.0)
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
identity_base = f"{auth_origin}/identitytoolkit.googleapis.com/v1"
|
|
165
|
+
uid, id_token = _create_or_sign_in_user(
|
|
166
|
+
client,
|
|
167
|
+
identity_base=identity_base,
|
|
168
|
+
email=email,
|
|
169
|
+
password=password,
|
|
170
|
+
web_api_key=web_api_key,
|
|
171
|
+
allow_existing=password_provided,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
data_payload = {"label": label}
|
|
175
|
+
if is_restricted:
|
|
176
|
+
data_payload["isRestricted"] = True
|
|
177
|
+
data_payload["spendLimit"] = spend_limit
|
|
178
|
+
if permissions is not None:
|
|
179
|
+
data_payload["permissions"] = permissions
|
|
180
|
+
|
|
181
|
+
function_url = f"{functions_origin}/{project_id}/{region}/create_api_key"
|
|
182
|
+
response = _request_json(
|
|
183
|
+
client,
|
|
184
|
+
method="POST",
|
|
185
|
+
url=function_url,
|
|
186
|
+
headers={"Authorization": f"Bearer {id_token}"},
|
|
187
|
+
json_body={"data": data_payload},
|
|
188
|
+
)
|
|
189
|
+
result = response.get("result") or response.get("data") or response
|
|
190
|
+
if not isinstance(result, dict):
|
|
191
|
+
raise EmulatorApiKeyError("Callable response missing result data.", payload=response)
|
|
192
|
+
api_key = result.get("key")
|
|
193
|
+
key_id = result.get("keyId")
|
|
194
|
+
key_prefix = result.get("keyPrefix")
|
|
195
|
+
if not api_key or not key_id:
|
|
196
|
+
raise EmulatorApiKeyError("Callable response missing key data.", payload=result)
|
|
197
|
+
|
|
198
|
+
return EmulatorApiKeyResult(
|
|
199
|
+
api_key=api_key,
|
|
200
|
+
key_id=key_id,
|
|
201
|
+
key_prefix=key_prefix or "",
|
|
202
|
+
label=str(result.get("label") or label),
|
|
203
|
+
user_id=uid,
|
|
204
|
+
email=email,
|
|
205
|
+
password=password,
|
|
206
|
+
id_token=id_token,
|
|
207
|
+
)
|
|
208
|
+
finally:
|
|
209
|
+
if owns_client:
|
|
210
|
+
client.close()
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _build_arg_parser():
|
|
214
|
+
parser = argparse.ArgumentParser(
|
|
215
|
+
description="Create an emulator API key and link it to an emulator Auth user.",
|
|
216
|
+
)
|
|
217
|
+
parser.add_argument("--label", help="Label for the API key (defaults to a random test label).")
|
|
218
|
+
parser.add_argument("--email", help="Email for the Auth emulator user (defaults to random).")
|
|
219
|
+
parser.add_argument("--password", help="Password for the Auth emulator user (defaults to random).")
|
|
220
|
+
parser.add_argument("--projectId", help=f"Firebase project id (default {DEFAULT_PROJECT_ID}).")
|
|
221
|
+
parser.add_argument("--authHost", help=f"Auth emulator host (default {DEFAULT_AUTH_HOST}).")
|
|
222
|
+
parser.add_argument("--functionsHost", help=f"Functions emulator host (default {DEFAULT_FUNCTIONS_HOST}).")
|
|
223
|
+
parser.add_argument("--webApiKey", help=f"Web API key (default {DEFAULT_WEB_API_KEY}).")
|
|
224
|
+
parser.add_argument("--region", help=f"Functions region (default {DEFAULT_REGION}).")
|
|
225
|
+
parser.add_argument("--restricted", action="store_true", help="Create a restricted key with spendLimit.")
|
|
226
|
+
parser.add_argument("--spendLimit", type=float, help="Spend limit for restricted keys.")
|
|
227
|
+
parser.add_argument(
|
|
228
|
+
"--permissions",
|
|
229
|
+
help='Optional permissions JSON, for example {"read": true, "write": false}.',
|
|
230
|
+
)
|
|
231
|
+
parser.add_argument("--json", action="store_true", help="Print JSON output for scripting.")
|
|
232
|
+
parser.add_argument("--show-id-token", action="store_true", help="Include the idToken in output.")
|
|
233
|
+
parser.add_argument("--show-password", action="store_true", help="Include the password in output.")
|
|
234
|
+
return parser
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _format_result(
|
|
238
|
+
result,
|
|
239
|
+
*,
|
|
240
|
+
show_password,
|
|
241
|
+
show_id_token,
|
|
242
|
+
):
|
|
243
|
+
payload = {
|
|
244
|
+
"apiKey": result.api_key,
|
|
245
|
+
"keyId": result.key_id,
|
|
246
|
+
"keyPrefix": result.key_prefix,
|
|
247
|
+
"label": result.label,
|
|
248
|
+
"userId": result.user_id,
|
|
249
|
+
"email": result.email,
|
|
250
|
+
}
|
|
251
|
+
if show_password:
|
|
252
|
+
payload["password"] = result.password
|
|
253
|
+
if show_id_token:
|
|
254
|
+
payload["idToken"] = result.id_token
|
|
255
|
+
return payload
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def main():
|
|
259
|
+
parser = _build_arg_parser()
|
|
260
|
+
args = parser.parse_args()
|
|
261
|
+
|
|
262
|
+
permissions = None
|
|
263
|
+
if args.permissions:
|
|
264
|
+
try:
|
|
265
|
+
permissions = json.loads(args.permissions)
|
|
266
|
+
except json.JSONDecodeError as exc:
|
|
267
|
+
raise SystemExit(f"permissions must be valid JSON. {exc}") from exc
|
|
268
|
+
|
|
269
|
+
result = create_emulator_api_key(
|
|
270
|
+
label=args.label,
|
|
271
|
+
email=args.email,
|
|
272
|
+
password=args.password,
|
|
273
|
+
project_id=args.projectId,
|
|
274
|
+
auth_emulator_host=args.authHost,
|
|
275
|
+
functions_emulator_host=args.functionsHost,
|
|
276
|
+
web_api_key=args.webApiKey,
|
|
277
|
+
region=args.region,
|
|
278
|
+
is_restricted=args.restricted,
|
|
279
|
+
spend_limit=args.spendLimit,
|
|
280
|
+
permissions=permissions,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
payload = _format_result(result, show_password=args.show_password, show_id_token=args.show_id_token)
|
|
284
|
+
|
|
285
|
+
if args.json:
|
|
286
|
+
print(json.dumps(payload, indent=2))
|
|
287
|
+
return 0
|
|
288
|
+
|
|
289
|
+
print("Created emulator API key.")
|
|
290
|
+
for key, value in payload.items():
|
|
291
|
+
print(f"{key}: {value}")
|
|
292
|
+
return 0
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
if __name__ == "__main__":
|
|
296
|
+
raise SystemExit(main())
|
diffio/types.py
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
class CreateProjectResponse:
|
|
2
|
+
def __init__(
|
|
3
|
+
self,
|
|
4
|
+
apiProjectId,
|
|
5
|
+
uploadUrl,
|
|
6
|
+
uploadMethod,
|
|
7
|
+
objectPath,
|
|
8
|
+
bucket,
|
|
9
|
+
expiresAt,
|
|
10
|
+
):
|
|
11
|
+
self.apiProjectId = apiProjectId
|
|
12
|
+
self.uploadUrl = uploadUrl
|
|
13
|
+
self.uploadMethod = uploadMethod
|
|
14
|
+
self.objectPath = objectPath
|
|
15
|
+
self.bucket = bucket
|
|
16
|
+
self.expiresAt = expiresAt
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def from_dict(cls, data):
|
|
20
|
+
return cls(
|
|
21
|
+
apiProjectId=data["apiProjectId"],
|
|
22
|
+
uploadUrl=data["uploadUrl"],
|
|
23
|
+
uploadMethod=data.get("uploadMethod") or "PUT",
|
|
24
|
+
objectPath=data["objectPath"],
|
|
25
|
+
bucket=data["bucket"],
|
|
26
|
+
expiresAt=data["expiresAt"],
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ProjectSummary:
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
apiProjectId,
|
|
34
|
+
status,
|
|
35
|
+
originalFileName,
|
|
36
|
+
contentType,
|
|
37
|
+
hasVideo,
|
|
38
|
+
generationCount,
|
|
39
|
+
createdAt,
|
|
40
|
+
updatedAt,
|
|
41
|
+
):
|
|
42
|
+
self.apiProjectId = apiProjectId
|
|
43
|
+
self.status = status
|
|
44
|
+
self.originalFileName = originalFileName
|
|
45
|
+
self.contentType = contentType
|
|
46
|
+
self.hasVideo = hasVideo
|
|
47
|
+
self.generationCount = generationCount
|
|
48
|
+
self.createdAt = createdAt
|
|
49
|
+
self.updatedAt = updatedAt
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def from_dict(cls, data):
|
|
53
|
+
return cls(
|
|
54
|
+
apiProjectId=data["apiProjectId"],
|
|
55
|
+
status=data.get("status") or "uploading",
|
|
56
|
+
originalFileName=data.get("originalFileName"),
|
|
57
|
+
contentType=data.get("contentType"),
|
|
58
|
+
hasVideo=bool(data.get("hasVideo")),
|
|
59
|
+
generationCount=int(data.get("generationCount") or 0),
|
|
60
|
+
createdAt=data.get("createdAt"),
|
|
61
|
+
updatedAt=data.get("updatedAt"),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ListProjectsResponse:
|
|
66
|
+
def __init__(self, projects):
|
|
67
|
+
self.projects = projects
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def from_dict(cls, data):
|
|
71
|
+
items = data.get("projects")
|
|
72
|
+
if not isinstance(items, list):
|
|
73
|
+
items = []
|
|
74
|
+
projects = [ProjectSummary.from_dict(item) for item in items if isinstance(item, dict)]
|
|
75
|
+
return cls(projects=projects)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class CreateGenerationResponse:
|
|
79
|
+
def __init__(self, generationId, apiProjectId, modelKey, status):
|
|
80
|
+
self.generationId = generationId
|
|
81
|
+
self.apiProjectId = apiProjectId
|
|
82
|
+
self.modelKey = modelKey
|
|
83
|
+
self.status = status
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def from_dict(cls, data):
|
|
87
|
+
return cls(
|
|
88
|
+
generationId=data["generationId"],
|
|
89
|
+
apiProjectId=data["apiProjectId"],
|
|
90
|
+
modelKey=data["modelKey"],
|
|
91
|
+
status=data["status"],
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ProjectGenerationSummary:
|
|
96
|
+
def __init__(self, generationId, status, modelKey, progress, createdAt, updatedAt):
|
|
97
|
+
self.generationId = generationId
|
|
98
|
+
self.status = status
|
|
99
|
+
self.modelKey = modelKey
|
|
100
|
+
self.progress = progress
|
|
101
|
+
self.createdAt = createdAt
|
|
102
|
+
self.updatedAt = updatedAt
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def from_dict(cls, data):
|
|
106
|
+
progress = data.get("progress")
|
|
107
|
+
return cls(
|
|
108
|
+
generationId=data["generationId"],
|
|
109
|
+
status=data.get("status") or "queued",
|
|
110
|
+
modelKey=data.get("modelKey"),
|
|
111
|
+
progress=int(progress) if progress is not None else None,
|
|
112
|
+
createdAt=data.get("createdAt"),
|
|
113
|
+
updatedAt=data.get("updatedAt"),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class ListProjectGenerationsResponse:
|
|
118
|
+
def __init__(self, apiProjectId, generations):
|
|
119
|
+
self.apiProjectId = apiProjectId
|
|
120
|
+
self.generations = generations
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def from_dict(cls, data):
|
|
124
|
+
items = data.get("generations")
|
|
125
|
+
if not isinstance(items, list):
|
|
126
|
+
items = []
|
|
127
|
+
generations = [ProjectGenerationSummary.from_dict(item) for item in items if isinstance(item, dict)]
|
|
128
|
+
return cls(
|
|
129
|
+
apiProjectId=data["apiProjectId"],
|
|
130
|
+
generations=generations,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class GenerationProgressStage:
|
|
135
|
+
def __init__(
|
|
136
|
+
self,
|
|
137
|
+
jobId,
|
|
138
|
+
jobState,
|
|
139
|
+
status,
|
|
140
|
+
progress,
|
|
141
|
+
statusMessage,
|
|
142
|
+
error,
|
|
143
|
+
errorDetails,
|
|
144
|
+
):
|
|
145
|
+
self.jobId = jobId
|
|
146
|
+
self.jobState = jobState
|
|
147
|
+
self.status = status
|
|
148
|
+
self.progress = progress
|
|
149
|
+
self.statusMessage = statusMessage
|
|
150
|
+
self.error = error
|
|
151
|
+
self.errorDetails = errorDetails
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def from_dict(cls, data):
|
|
155
|
+
return cls(
|
|
156
|
+
jobId=data.get("jobId"),
|
|
157
|
+
jobState=data.get("jobState"),
|
|
158
|
+
status=data.get("status") or "pending",
|
|
159
|
+
progress=int(data.get("progress") or 0),
|
|
160
|
+
statusMessage=data.get("statusMessage"),
|
|
161
|
+
error=data.get("error"),
|
|
162
|
+
errorDetails=data.get("errorDetails"),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class GenerationProgressResponse:
|
|
167
|
+
def __init__(
|
|
168
|
+
self,
|
|
169
|
+
generationId,
|
|
170
|
+
apiProjectId,
|
|
171
|
+
status,
|
|
172
|
+
hasVideo,
|
|
173
|
+
preProcessing,
|
|
174
|
+
inference,
|
|
175
|
+
restoredVideo,
|
|
176
|
+
error,
|
|
177
|
+
errorDetails,
|
|
178
|
+
):
|
|
179
|
+
self.generationId = generationId
|
|
180
|
+
self.apiProjectId = apiProjectId
|
|
181
|
+
self.status = status
|
|
182
|
+
self.hasVideo = hasVideo
|
|
183
|
+
self.preProcessing = preProcessing
|
|
184
|
+
self.inference = inference
|
|
185
|
+
self.restoredVideo = restoredVideo
|
|
186
|
+
self.error = error
|
|
187
|
+
self.errorDetails = errorDetails
|
|
188
|
+
|
|
189
|
+
@classmethod
|
|
190
|
+
def from_dict(cls, data):
|
|
191
|
+
restored_video = data.get("restoredVideo")
|
|
192
|
+
return cls(
|
|
193
|
+
generationId=data["generationId"],
|
|
194
|
+
apiProjectId=data["apiProjectId"],
|
|
195
|
+
status=data["status"],
|
|
196
|
+
hasVideo=bool(data.get("hasVideo")),
|
|
197
|
+
preProcessing=GenerationProgressStage.from_dict(data["preProcessing"]),
|
|
198
|
+
inference=GenerationProgressStage.from_dict(data["inference"]),
|
|
199
|
+
restoredVideo=(GenerationProgressStage.from_dict(restored_video) if restored_video else None),
|
|
200
|
+
error=data.get("error"),
|
|
201
|
+
errorDetails=data.get("errorDetails"),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class GenerationDownloadResponse:
|
|
206
|
+
def __init__(
|
|
207
|
+
self,
|
|
208
|
+
generationId,
|
|
209
|
+
apiProjectId,
|
|
210
|
+
downloadType,
|
|
211
|
+
downloadUrl,
|
|
212
|
+
fileName,
|
|
213
|
+
storagePath,
|
|
214
|
+
bucket,
|
|
215
|
+
mimeType,
|
|
216
|
+
):
|
|
217
|
+
self.generationId = generationId
|
|
218
|
+
self.apiProjectId = apiProjectId
|
|
219
|
+
self.downloadType = downloadType
|
|
220
|
+
self.downloadUrl = downloadUrl
|
|
221
|
+
self.fileName = fileName
|
|
222
|
+
self.storagePath = storagePath
|
|
223
|
+
self.bucket = bucket
|
|
224
|
+
self.mimeType = mimeType
|
|
225
|
+
|
|
226
|
+
@classmethod
|
|
227
|
+
def from_dict(cls, data):
|
|
228
|
+
return cls(
|
|
229
|
+
generationId=data["generationId"],
|
|
230
|
+
apiProjectId=data["apiProjectId"],
|
|
231
|
+
downloadType=data["downloadType"],
|
|
232
|
+
downloadUrl=data["downloadUrl"],
|
|
233
|
+
fileName=data["fileName"],
|
|
234
|
+
storagePath=data["storagePath"],
|
|
235
|
+
bucket=data["bucket"],
|
|
236
|
+
mimeType=data["mimeType"],
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class WebhookPortalResponse:
|
|
241
|
+
def __init__(self, portalUrl, mode, apiKeyId=None):
|
|
242
|
+
self.portalUrl = portalUrl
|
|
243
|
+
self.mode = mode
|
|
244
|
+
self.apiKeyId = apiKeyId
|
|
245
|
+
|
|
246
|
+
@classmethod
|
|
247
|
+
def from_dict(cls, data):
|
|
248
|
+
return cls(
|
|
249
|
+
portalUrl=data["portalUrl"],
|
|
250
|
+
mode=data.get("mode"),
|
|
251
|
+
apiKeyId=data.get("apiKeyId"),
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class WebhookTestEventResponse:
|
|
256
|
+
def __init__(self, svixMessageId, eventId, eventType, mode, apiKeyId=None):
|
|
257
|
+
self.svixMessageId = svixMessageId
|
|
258
|
+
self.eventId = eventId
|
|
259
|
+
self.eventType = eventType
|
|
260
|
+
self.mode = mode
|
|
261
|
+
self.apiKeyId = apiKeyId
|
|
262
|
+
|
|
263
|
+
@classmethod
|
|
264
|
+
def from_dict(cls, data):
|
|
265
|
+
return cls(
|
|
266
|
+
svixMessageId=data["svixMessageId"],
|
|
267
|
+
eventId=data["eventId"],
|
|
268
|
+
eventType=data["eventType"],
|
|
269
|
+
mode=data.get("mode"),
|
|
270
|
+
apiKeyId=data.get("apiKeyId"),
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class GenerationWebhookEvent:
|
|
275
|
+
def __init__(
|
|
276
|
+
self,
|
|
277
|
+
eventType,
|
|
278
|
+
eventId,
|
|
279
|
+
createdAt,
|
|
280
|
+
apiKeyId,
|
|
281
|
+
apiProjectId,
|
|
282
|
+
generationId,
|
|
283
|
+
status,
|
|
284
|
+
hasVideo,
|
|
285
|
+
modelKey,
|
|
286
|
+
error,
|
|
287
|
+
errorDetails,
|
|
288
|
+
):
|
|
289
|
+
self.eventType = eventType
|
|
290
|
+
self.eventId = eventId
|
|
291
|
+
self.createdAt = createdAt
|
|
292
|
+
self.apiKeyId = apiKeyId
|
|
293
|
+
self.apiProjectId = apiProjectId
|
|
294
|
+
self.generationId = generationId
|
|
295
|
+
self.status = status
|
|
296
|
+
self.hasVideo = hasVideo
|
|
297
|
+
self.modelKey = modelKey
|
|
298
|
+
self.error = error
|
|
299
|
+
self.errorDetails = errorDetails
|
|
300
|
+
|
|
301
|
+
@classmethod
|
|
302
|
+
def from_dict(cls, data):
|
|
303
|
+
return cls(
|
|
304
|
+
eventType=data["eventType"],
|
|
305
|
+
eventId=data["eventId"],
|
|
306
|
+
createdAt=data["createdAt"],
|
|
307
|
+
apiKeyId=data["apiKeyId"],
|
|
308
|
+
apiProjectId=data.get("apiProjectId"),
|
|
309
|
+
generationId=data["generationId"],
|
|
310
|
+
status=data["status"],
|
|
311
|
+
hasVideo=data.get("hasVideo"),
|
|
312
|
+
modelKey=data.get("modelKey"),
|
|
313
|
+
error=data.get("error"),
|
|
314
|
+
errorDetails=data.get("errorDetails"),
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class AudioIsolationResult:
|
|
319
|
+
def __init__(self, project, generation):
|
|
320
|
+
self.project = project
|
|
321
|
+
self.generation = generation
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
ModelKey = ("diffio-2", "diffio-2-flash", "diffio-3")
|
|
325
|
+
DownloadType = ("audio", "mp3", "video")
|
|
326
|
+
WebhookMode = ("test", "live")
|
|
327
|
+
WebhookEventType = (
|
|
328
|
+
"generation.queued",
|
|
329
|
+
"generation.processing",
|
|
330
|
+
"generation.failed",
|
|
331
|
+
"generation.completed",
|
|
332
|
+
)
|
|
333
|
+
GenerationWebhookStatus = ("queued", "processing", "error", "complete")
|