magic_hour 0.23.0__py3-none-any.whl → 0.25.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.
Potentially problematic release.
This version of magic_hour might be problematic. Click here for more details.
- magic_hour/client.py +2 -2
- magic_hour/core/__init__.py +6 -10
- magic_hour/core/auth.py +136 -96
- magic_hour/core/query.py +5 -6
- magic_hour/environment.py +1 -1
- magic_hour/resources/v1/ai_clothes_changer/README.md +17 -0
- magic_hour/resources/v1/ai_face_editor/README.md +18 -0
- magic_hour/resources/v1/ai_gif_generator/README.md +17 -0
- magic_hour/resources/v1/ai_headshot_generator/README.md +18 -0
- magic_hour/resources/v1/ai_image_editor/README.md +52 -0
- magic_hour/resources/v1/ai_image_editor/__init__.py +4 -0
- magic_hour/resources/v1/ai_image_editor/client.py +125 -0
- magic_hour/resources/v1/ai_image_generator/README.md +21 -2
- magic_hour/resources/v1/ai_image_generator/client.py +2 -2
- magic_hour/resources/v1/ai_image_upscaler/README.md +19 -0
- magic_hour/resources/v1/ai_meme_generator/README.md +17 -0
- magic_hour/resources/v1/ai_photo_editor/README.md +20 -0
- magic_hour/resources/v1/ai_qr_code_generator/README.md +18 -0
- magic_hour/resources/v1/ai_talking_photo/README.md +20 -0
- magic_hour/resources/v1/animation/README.md +22 -0
- magic_hour/resources/v1/client.py +6 -0
- magic_hour/resources/v1/face_swap/README.md +21 -0
- magic_hour/resources/v1/face_swap_photo/README.md +17 -0
- magic_hour/resources/v1/files/upload_urls/README.md +19 -4
- magic_hour/resources/v1/files/upload_urls/client.py +6 -8
- magic_hour/resources/v1/image_background_remover/README.md +17 -0
- magic_hour/resources/v1/image_projects/README.md +24 -0
- magic_hour/resources/v1/image_to_video/README.md +21 -0
- magic_hour/resources/v1/lip_sync/README.md +22 -0
- magic_hour/resources/v1/photo_colorizer/README.md +17 -0
- magic_hour/resources/v1/text_to_video/README.md +19 -0
- magic_hour/resources/v1/video_projects/README.md +24 -0
- magic_hour/resources/v1/video_to_video/README.md +23 -0
- magic_hour/types/models/__init__.py +2 -0
- magic_hour/types/models/v1_ai_image_editor_create_response.py +33 -0
- magic_hour/types/models/v1_image_projects_get_response.py +1 -1
- magic_hour/types/params/__init__.py +18 -0
- magic_hour/types/params/v1_ai_image_editor_create_body.py +49 -0
- magic_hour/types/params/v1_ai_image_editor_create_body_assets.py +28 -0
- magic_hour/types/params/v1_ai_image_editor_create_body_style.py +28 -0
- magic_hour/types/params/v1_ai_image_generator_create_body_style.py +83 -0
- {magic_hour-0.23.0.dist-info → magic_hour-0.25.0.dist-info}/METADATA +5 -1
- {magic_hour-0.23.0.dist-info → magic_hour-0.25.0.dist-info}/RECORD +45 -38
- {magic_hour-0.23.0.dist-info → magic_hour-0.25.0.dist-info}/LICENSE +0 -0
- {magic_hour-0.23.0.dist-info → magic_hour-0.25.0.dist-info}/WHEEL +0 -0
magic_hour/client.py
CHANGED
|
@@ -23,7 +23,7 @@ class Client:
|
|
|
23
23
|
if httpx_client is None
|
|
24
24
|
else httpx_client,
|
|
25
25
|
)
|
|
26
|
-
self._base_client.register_auth("bearerAuth", AuthBearer(
|
|
26
|
+
self._base_client.register_auth("bearerAuth", AuthBearer(token=token))
|
|
27
27
|
self.v1 = V1Client(base_client=self._base_client)
|
|
28
28
|
|
|
29
29
|
|
|
@@ -44,5 +44,5 @@ class AsyncClient:
|
|
|
44
44
|
if httpx_client is None
|
|
45
45
|
else httpx_client,
|
|
46
46
|
)
|
|
47
|
-
self._base_client.register_auth("bearerAuth", AuthBearer(
|
|
47
|
+
self._base_client.register_auth("bearerAuth", AuthBearer(token=token))
|
|
48
48
|
self.v1 = AsyncV1Client(base_client=self._base_client)
|
magic_hour/core/__init__.py
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
from .api_error import ApiError
|
|
2
2
|
from .auth import (
|
|
3
|
-
|
|
3
|
+
AuthKey,
|
|
4
4
|
AuthBasic,
|
|
5
5
|
AuthBearer,
|
|
6
6
|
AuthProvider,
|
|
7
|
-
AuthKeyCookie,
|
|
8
|
-
AuthKeyHeader,
|
|
9
7
|
GrantType,
|
|
10
8
|
OAuth2,
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
OAuth2ClientCredentials,
|
|
10
|
+
OAuth2Password,
|
|
13
11
|
)
|
|
14
12
|
from .base_client import AsyncBaseClient, BaseClient, SyncBaseClient
|
|
15
13
|
from .binary_response import BinaryResponse
|
|
@@ -32,16 +30,14 @@ __all__ = [
|
|
|
32
30
|
"RequestOptions",
|
|
33
31
|
"default_request_options",
|
|
34
32
|
"SyncBaseClient",
|
|
35
|
-
"
|
|
33
|
+
"AuthKey",
|
|
36
34
|
"AuthBasic",
|
|
37
35
|
"AuthBearer",
|
|
38
36
|
"AuthProvider",
|
|
39
|
-
"AuthKeyCookie",
|
|
40
|
-
"AuthKeyHeader",
|
|
41
37
|
"GrantType",
|
|
42
38
|
"OAuth2",
|
|
43
|
-
"
|
|
44
|
-
"
|
|
39
|
+
"OAuth2ClientCredentials",
|
|
40
|
+
"OAuth2Password",
|
|
45
41
|
"to_encodable",
|
|
46
42
|
"to_form_urlencoded",
|
|
47
43
|
"filter_not_given",
|
magic_hour/core/auth.py
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
"""Generated by Sideko (sideko.dev)"""
|
|
2
|
+
|
|
1
3
|
import abc
|
|
2
4
|
import datetime
|
|
3
|
-
from typing import Any, Dict, TypedDict, Optional, List, Tuple, Literal
|
|
5
|
+
from typing import Any, Dict, TypedDict, Optional, List, Tuple, Literal, Union, cast
|
|
4
6
|
|
|
5
7
|
import jsonpointer # type: ignore
|
|
6
8
|
import httpx
|
|
7
|
-
from pydantic import BaseModel
|
|
8
9
|
from .request import RequestConfig
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
class AuthProvider(abc.ABC
|
|
12
|
+
class AuthProvider(abc.ABC):
|
|
12
13
|
"""
|
|
13
14
|
Abstract base class defining the interface for authentication providers.
|
|
14
15
|
|
|
@@ -49,6 +50,13 @@ class AuthBasic(AuthProvider):
|
|
|
49
50
|
username: Optional[str]
|
|
50
51
|
password: Optional[str]
|
|
51
52
|
|
|
53
|
+
def __init__(
|
|
54
|
+
self, *, username: Optional[str] = None, password: Optional[str] = None
|
|
55
|
+
):
|
|
56
|
+
super().__init__()
|
|
57
|
+
self.username = username
|
|
58
|
+
self.password = password
|
|
59
|
+
|
|
52
60
|
def add_to_request(self, cfg: RequestConfig) -> RequestConfig:
|
|
53
61
|
"""
|
|
54
62
|
Adds Basic Authentication credentials to the request configuration.
|
|
@@ -71,10 +79,14 @@ class AuthBearer(AuthProvider):
|
|
|
71
79
|
Implements Bearer token authentication.
|
|
72
80
|
|
|
73
81
|
Adds a Bearer token to the request's Authorization header following
|
|
74
|
-
|
|
82
|
+
a 'Bearer ' prefix
|
|
75
83
|
"""
|
|
76
84
|
|
|
77
|
-
|
|
85
|
+
token: Optional[str]
|
|
86
|
+
|
|
87
|
+
def __init__(self, *, token: Optional[str] = None):
|
|
88
|
+
super().__init__()
|
|
89
|
+
self.token = token
|
|
78
90
|
|
|
79
91
|
def add_to_request(self, cfg: RequestConfig) -> RequestConfig:
|
|
80
92
|
"""
|
|
@@ -82,9 +94,9 @@ class AuthBearer(AuthProvider):
|
|
|
82
94
|
|
|
83
95
|
Only modifies the configuration if a token value is provided.
|
|
84
96
|
"""
|
|
85
|
-
if self.
|
|
97
|
+
if self.token is not None:
|
|
86
98
|
headers = cfg.get("headers", dict())
|
|
87
|
-
headers["Authorization"] = f"Bearer {self.
|
|
99
|
+
headers["Authorization"] = f"Bearer {self.token}"
|
|
88
100
|
cfg["headers"] = headers
|
|
89
101
|
return cfg
|
|
90
102
|
|
|
@@ -92,90 +104,51 @@ class AuthBearer(AuthProvider):
|
|
|
92
104
|
"""
|
|
93
105
|
Sets value as the bearer token
|
|
94
106
|
"""
|
|
95
|
-
self.
|
|
107
|
+
self.token = val
|
|
96
108
|
|
|
97
109
|
|
|
98
|
-
class
|
|
110
|
+
class AuthKey(AuthProvider):
|
|
99
111
|
"""
|
|
100
|
-
Implements query
|
|
112
|
+
Implements query, header, or cookie based authentication.
|
|
101
113
|
|
|
102
|
-
Adds an authentication token
|
|
103
|
-
configurable parameter name.
|
|
114
|
+
Adds an authentication token to the request in the configured location
|
|
104
115
|
"""
|
|
105
116
|
|
|
106
|
-
|
|
117
|
+
name: str
|
|
118
|
+
location: Literal["query", "header", "cookie"]
|
|
107
119
|
val: Optional[str]
|
|
108
120
|
|
|
109
|
-
def
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return cfg
|
|
120
|
-
|
|
121
|
-
def set_value(self, val: Optional[str]) -> None:
|
|
122
|
-
"""
|
|
123
|
-
Sets value as the key
|
|
124
|
-
"""
|
|
121
|
+
def __init__(
|
|
122
|
+
self,
|
|
123
|
+
*,
|
|
124
|
+
name: str,
|
|
125
|
+
location: Literal["query", "header", "cookie"],
|
|
126
|
+
val: Optional[str] = None,
|
|
127
|
+
):
|
|
128
|
+
super().__init__()
|
|
129
|
+
self.name = name
|
|
130
|
+
self.location = location
|
|
125
131
|
self.val = val
|
|
126
132
|
|
|
127
|
-
|
|
128
|
-
class AuthKeyHeader(AuthProvider):
|
|
129
|
-
"""
|
|
130
|
-
Implements header-based authentication.
|
|
131
|
-
|
|
132
|
-
Adds an authentication token or key as a custom header with a
|
|
133
|
-
configurable header name.
|
|
134
|
-
"""
|
|
135
|
-
|
|
136
|
-
header_name: str
|
|
137
|
-
val: Optional[str]
|
|
138
|
-
|
|
139
133
|
def add_to_request(self, cfg: RequestConfig) -> RequestConfig:
|
|
140
134
|
"""
|
|
141
|
-
Adds authentication value as a
|
|
142
|
-
|
|
143
|
-
Only modifies the configuration if a value is provided.
|
|
135
|
+
Adds authentication value as a query/header/cookie parameter
|
|
144
136
|
"""
|
|
145
|
-
if self.val is
|
|
137
|
+
if self.val is None:
|
|
138
|
+
return cfg
|
|
139
|
+
elif self.location == "query":
|
|
140
|
+
params = cfg.get("params", dict())
|
|
141
|
+
params[self.name] = self.val
|
|
142
|
+
cfg["params"] = params
|
|
143
|
+
elif self.location == "header":
|
|
146
144
|
headers = cfg.get("headers", {})
|
|
147
|
-
headers[self.
|
|
145
|
+
headers[self.name] = self.val
|
|
148
146
|
cfg["headers"] = headers
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def set_value(self, val: Optional[str]) -> None:
|
|
152
|
-
"""
|
|
153
|
-
Sets value as the key
|
|
154
|
-
"""
|
|
155
|
-
self.val = val
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
class AuthKeyCookie(AuthProvider):
|
|
159
|
-
"""
|
|
160
|
-
Implements cookie-based authentication.
|
|
161
|
-
|
|
162
|
-
Adds an authentication token or key as a cookie with a
|
|
163
|
-
configurable cookie name.
|
|
164
|
-
"""
|
|
165
|
-
|
|
166
|
-
cookie_name: str
|
|
167
|
-
val: Optional[str]
|
|
168
|
-
|
|
169
|
-
def add_to_request(self, cfg: RequestConfig) -> RequestConfig:
|
|
170
|
-
"""
|
|
171
|
-
Adds authentication value as a cookie.
|
|
172
|
-
|
|
173
|
-
Only modifies the configuration if a value is provided.
|
|
174
|
-
"""
|
|
175
|
-
if self.val is not None:
|
|
147
|
+
else:
|
|
176
148
|
cookies = cfg.get("cookies", dict())
|
|
177
|
-
cookies[self.
|
|
149
|
+
cookies[self.name] = self.val
|
|
178
150
|
cfg["cookies"] = cookies
|
|
151
|
+
|
|
179
152
|
return cfg
|
|
180
153
|
|
|
181
154
|
def set_value(self, val: Optional[str]) -> None:
|
|
@@ -185,7 +158,12 @@ class AuthKeyCookie(AuthProvider):
|
|
|
185
158
|
self.val = val
|
|
186
159
|
|
|
187
160
|
|
|
188
|
-
|
|
161
|
+
GrantType = Literal["password", "client_credentials"]
|
|
162
|
+
CredentialsLocation = Literal["request_body", "basic_authorization_header"]
|
|
163
|
+
BodyContent = Literal["form", "json"]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class OAuth2Password(TypedDict, total=True):
|
|
189
167
|
"""
|
|
190
168
|
OAuth2 authentication form for a password flow
|
|
191
169
|
|
|
@@ -197,11 +175,16 @@ class OAuth2PasswordForm(TypedDict, total=True):
|
|
|
197
175
|
password: str
|
|
198
176
|
client_id: Optional[str]
|
|
199
177
|
client_secret: Optional[str]
|
|
200
|
-
grant_type: Optional[str]
|
|
178
|
+
grant_type: Optional[Union[GrantType, str]]
|
|
201
179
|
scope: Optional[List[str]]
|
|
202
180
|
|
|
181
|
+
token_url: Optional[str]
|
|
182
|
+
"""
|
|
183
|
+
Overrides the default token url
|
|
184
|
+
"""
|
|
185
|
+
|
|
203
186
|
|
|
204
|
-
class
|
|
187
|
+
class OAuth2ClientCredentials(TypedDict, total=True):
|
|
205
188
|
"""
|
|
206
189
|
OAuth2 authentication form for a client credentials flow
|
|
207
190
|
|
|
@@ -211,13 +194,13 @@ class OAuth2ClientCredentialsForm(TypedDict, total=True):
|
|
|
211
194
|
|
|
212
195
|
client_id: str
|
|
213
196
|
client_secret: str
|
|
214
|
-
grant_type: Optional[str]
|
|
197
|
+
grant_type: Optional[Union[GrantType, str]]
|
|
215
198
|
scope: Optional[List[str]]
|
|
216
199
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
200
|
+
token_url: Optional[str]
|
|
201
|
+
"""
|
|
202
|
+
Overrides the default token url
|
|
203
|
+
"""
|
|
221
204
|
|
|
222
205
|
|
|
223
206
|
class OAuth2(AuthProvider):
|
|
@@ -228,6 +211,7 @@ class OAuth2(AuthProvider):
|
|
|
228
211
|
"""
|
|
229
212
|
|
|
230
213
|
# OAuth2 provider configuration
|
|
214
|
+
base_url: str
|
|
231
215
|
token_url: str
|
|
232
216
|
access_token_pointer: str
|
|
233
217
|
expires_in_pointer: str
|
|
@@ -236,28 +220,76 @@ class OAuth2(AuthProvider):
|
|
|
236
220
|
request_mutator: AuthProvider
|
|
237
221
|
|
|
238
222
|
# OAuth2 access token request values
|
|
239
|
-
grant_type: GrantType
|
|
240
|
-
username: Optional[str]
|
|
241
|
-
password: Optional[str]
|
|
242
|
-
client_id: Optional[str]
|
|
243
|
-
client_secret: Optional[str]
|
|
244
|
-
scope: Optional[List[str]]
|
|
223
|
+
grant_type: Union[GrantType, str]
|
|
224
|
+
username: Optional[str]
|
|
225
|
+
password: Optional[str]
|
|
226
|
+
client_id: Optional[str]
|
|
227
|
+
client_secret: Optional[str]
|
|
228
|
+
scope: Optional[List[str]]
|
|
245
229
|
|
|
246
230
|
# access_token storage
|
|
247
|
-
access_token: Optional[str]
|
|
248
|
-
expires_at: Optional[datetime.datetime]
|
|
231
|
+
access_token: Optional[str]
|
|
232
|
+
expires_at: Optional[datetime.datetime]
|
|
233
|
+
|
|
234
|
+
def __init__(
|
|
235
|
+
self,
|
|
236
|
+
*,
|
|
237
|
+
base_url: str,
|
|
238
|
+
default_token_url: str,
|
|
239
|
+
access_token_pointer: str,
|
|
240
|
+
expires_in_pointer: str,
|
|
241
|
+
credentials_location: CredentialsLocation,
|
|
242
|
+
body_content: BodyContent,
|
|
243
|
+
request_mutator: AuthProvider,
|
|
244
|
+
form: Optional[Union[OAuth2Password, OAuth2ClientCredentials]] = None,
|
|
245
|
+
):
|
|
246
|
+
super().__init__()
|
|
247
|
+
|
|
248
|
+
form_data: Union[OAuth2Password, OAuth2ClientCredentials] = form or cast(
|
|
249
|
+
OAuth2ClientCredentials, {}
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
self.base_url = base_url
|
|
253
|
+
self.token_url = form_data.get("token_url") or default_token_url
|
|
254
|
+
self.access_token_pointer = access_token_pointer
|
|
255
|
+
self.expires_in_pointer = expires_in_pointer
|
|
256
|
+
self.credentials_location = credentials_location
|
|
257
|
+
self.body_content = body_content
|
|
258
|
+
self.request_mutator = request_mutator
|
|
259
|
+
|
|
260
|
+
default_grant_type: GrantType = (
|
|
261
|
+
"password"
|
|
262
|
+
if form_data.get("username") is not None
|
|
263
|
+
else "client_credentials"
|
|
264
|
+
)
|
|
265
|
+
self.grant_type = form_data.get("grant_type") or default_grant_type
|
|
266
|
+
self.username = cast(Optional[str], form_data.get("username"))
|
|
267
|
+
self.password = cast(Optional[str], form_data.get("password"))
|
|
268
|
+
self.client_id = form_data.get("client_id")
|
|
269
|
+
self.client_secret = form_data.get("client_secret")
|
|
270
|
+
self.scope = form_data.get("scope")
|
|
271
|
+
|
|
272
|
+
self.access_token = None
|
|
273
|
+
self.expires_at = None
|
|
249
274
|
|
|
250
275
|
def _refresh(self) -> Tuple[str, datetime.datetime]:
|
|
251
|
-
|
|
252
|
-
|
|
276
|
+
# build token url using base_url if relative
|
|
277
|
+
url = self.token_url
|
|
278
|
+
if url.startswith("/"):
|
|
279
|
+
url = f"{self.base_url.strip('/')}/{self.token_url.strip('/')}"
|
|
280
|
+
|
|
281
|
+
req_cfg: Dict[str, Any] = {"url": url}
|
|
282
|
+
req_data: Dict[str, str] = {"grant_type": self.grant_type}
|
|
253
283
|
|
|
254
284
|
# add client credentials
|
|
255
285
|
if self.client_id is not None or self.client_secret is not None:
|
|
256
286
|
if self.credentials_location == "basic_authorization_header":
|
|
257
|
-
req_cfg["auth"] = (self.client_id, self.client_secret)
|
|
287
|
+
req_cfg["auth"] = (self.client_id or "", self.client_secret or "")
|
|
258
288
|
else:
|
|
259
|
-
|
|
260
|
-
|
|
289
|
+
if self.client_id is not None:
|
|
290
|
+
req_data["client_id"] = self.client_id
|
|
291
|
+
if self.client_secret is not None:
|
|
292
|
+
req_data["client_secret"] = self.client_secret
|
|
261
293
|
|
|
262
294
|
# construct request data
|
|
263
295
|
if self.username is not None:
|
|
@@ -298,10 +330,18 @@ class OAuth2(AuthProvider):
|
|
|
298
330
|
return (access_token, expires_at)
|
|
299
331
|
|
|
300
332
|
def add_to_request(self, cfg: RequestConfig) -> RequestConfig:
|
|
333
|
+
if (
|
|
334
|
+
self.username is None
|
|
335
|
+
and self.password is None
|
|
336
|
+
and self.client_id is None
|
|
337
|
+
and self.client_secret is None
|
|
338
|
+
):
|
|
339
|
+
# provider is not configured to make an oauth token request
|
|
340
|
+
return cfg
|
|
341
|
+
|
|
301
342
|
token_expired = (
|
|
302
343
|
self.expires_at is not None and self.expires_at <= datetime.datetime.now()
|
|
303
344
|
)
|
|
304
|
-
|
|
305
345
|
if self.access_token is None or token_expired:
|
|
306
346
|
access_token, expires_at = self._refresh()
|
|
307
347
|
self.expires_at = expires_at
|
magic_hour/core/query.py
CHANGED
|
@@ -2,7 +2,6 @@ import json
|
|
|
2
2
|
|
|
3
3
|
from typing import Any, Dict, Union
|
|
4
4
|
from typing_extensions import Literal, Sequence
|
|
5
|
-
from urllib.parse import quote_plus, quote
|
|
6
5
|
|
|
7
6
|
import httpx
|
|
8
7
|
|
|
@@ -47,19 +46,19 @@ def _encode_form(params: QueryParams, name: str, value: Any, explode: bool):
|
|
|
47
46
|
"""
|
|
48
47
|
if isinstance(value, list) and not explode:
|
|
49
48
|
# non-explode form lists should be encoded like /users?id=3,4,5
|
|
50
|
-
params[name] =
|
|
49
|
+
params[name] = ",".join(map(_query_str, value))
|
|
51
50
|
elif isinstance(value, dict):
|
|
52
51
|
if explode:
|
|
53
52
|
# explode form objects should be encoded like /users?key0=val0&key1=val1
|
|
54
53
|
# the input param name will be omitted
|
|
55
54
|
for k, v in value.items():
|
|
56
|
-
params[k] =
|
|
55
|
+
params[k] = _query_str(v)
|
|
57
56
|
else:
|
|
58
57
|
# non-explode form objects should be encoded like /users?id=key0,val0,key1,val1
|
|
59
58
|
encoded_chunks = []
|
|
60
59
|
for k, v in value.items():
|
|
61
60
|
encoded_chunks.extend([str(k), _query_str(v)])
|
|
62
|
-
params[name] =
|
|
61
|
+
params[name] = ",".join(encoded_chunks)
|
|
63
62
|
else:
|
|
64
63
|
params[name] = value
|
|
65
64
|
|
|
@@ -71,7 +70,7 @@ def _encode_spaced_delimited(params: QueryParams, name: str, value: Any, explode
|
|
|
71
70
|
"""
|
|
72
71
|
if isinstance(value, list) and not explode:
|
|
73
72
|
# non-explode spaceDelimited lists should be encoded like /users?id=3%204%205
|
|
74
|
-
params[name] =
|
|
73
|
+
params[name] = " ".join(map(_query_str, value))
|
|
75
74
|
else:
|
|
76
75
|
# according to the docs, spaceDelimited + explode=false only effects lists,
|
|
77
76
|
# all other encodings are marked as n/a or are the same as `form` style
|
|
@@ -86,7 +85,7 @@ def _encode_pipe_delimited(params: QueryParams, name: str, value: Any, explode:
|
|
|
86
85
|
"""
|
|
87
86
|
if isinstance(value, list) and not explode:
|
|
88
87
|
# non-explode pipeDelimited lists should be encoded like /users?id=3|4|5
|
|
89
|
-
params[name] =
|
|
88
|
+
params[name] = "|".join(map(_query_str, value))
|
|
90
89
|
else:
|
|
91
90
|
# according to the docs, pipeDelimited + explode=false only effects lists,
|
|
92
91
|
# all other encodings are marked as n/a or are the same as `form` style
|
magic_hour/environment.py
CHANGED
|
@@ -6,7 +6,7 @@ class Environment(enum.Enum):
|
|
|
6
6
|
"""Pre-defined base URLs for the API"""
|
|
7
7
|
|
|
8
8
|
ENVIRONMENT = "https://api.magichour.ai"
|
|
9
|
-
MOCK_SERVER = "https://api.sideko.dev/v1/mock/magichour/magic-hour/0.
|
|
9
|
+
MOCK_SERVER = "https://api.sideko.dev/v1/mock/magichour/magic-hour/0.25.0"
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def _get_base_url(
|
|
@@ -5,6 +5,13 @@ Change outfits in photos in seconds with just a photo reference. Each photo cost
|
|
|
5
5
|
|
|
6
6
|
**API Endpoint**: `POST /v1/ai-clothes-changer`
|
|
7
7
|
|
|
8
|
+
#### Parameters
|
|
9
|
+
|
|
10
|
+
| Parameter | Required | Description | Example |
|
|
11
|
+
|-----------|:--------:|-------------|--------|
|
|
12
|
+
| `assets` | ✓ | Provide the assets for clothes changer | `{"garment_file_path": "api-assets/id/outfit.png", "garment_type": "dresses", "person_file_path": "api-assets/id/model.png"}` |
|
|
13
|
+
| `name` | ✗ | The name of image | `"Clothes Changer image"` |
|
|
14
|
+
|
|
8
15
|
#### Synchronous Client
|
|
9
16
|
|
|
10
17
|
```python
|
|
@@ -20,6 +27,7 @@ res = client.v1.ai_clothes_changer.create(
|
|
|
20
27
|
},
|
|
21
28
|
name="Clothes Changer image",
|
|
22
29
|
)
|
|
30
|
+
|
|
23
31
|
```
|
|
24
32
|
|
|
25
33
|
#### Asynchronous Client
|
|
@@ -37,4 +45,13 @@ res = await client.v1.ai_clothes_changer.create(
|
|
|
37
45
|
},
|
|
38
46
|
name="Clothes Changer image",
|
|
39
47
|
)
|
|
48
|
+
|
|
40
49
|
```
|
|
50
|
+
|
|
51
|
+
#### Response
|
|
52
|
+
|
|
53
|
+
##### Type
|
|
54
|
+
[V1AiClothesChangerCreateResponse](/magic_hour/types/models/v1_ai_clothes_changer_create_response.py)
|
|
55
|
+
|
|
56
|
+
##### Example
|
|
57
|
+
`{"credits_charged": 25, "frame_cost": 25, "id": "clx7uu86w0a5qp55yxz315r6r"}`
|
|
@@ -5,6 +5,14 @@ Edit facial features of an image using AI. Each edit costs 1 frame. The height/w
|
|
|
5
5
|
|
|
6
6
|
**API Endpoint**: `POST /v1/ai-face-editor`
|
|
7
7
|
|
|
8
|
+
#### Parameters
|
|
9
|
+
|
|
10
|
+
| Parameter | Required | Description | Example |
|
|
11
|
+
|-----------|:--------:|-------------|--------|
|
|
12
|
+
| `assets` | ✓ | Provide the assets for face editor | `{"image_file_path": "api-assets/id/1234.png"}` |
|
|
13
|
+
| `style` | ✓ | Face editing parameters | `{"enhance_face": False, "eye_gaze_horizontal": 0.0, "eye_gaze_vertical": 0.0, "eye_open_ratio": 0.0, "eyebrow_direction": 0.0, "head_pitch": 0.0, "head_roll": 0.0, "head_yaw": 0.0, "lip_open_ratio": 0.0, "mouth_grim": 0.0, "mouth_position_horizontal": 0.0, "mouth_position_vertical": 0.0, "mouth_pout": 0.0, "mouth_purse": 0.0, "mouth_smile": 0.0}` |
|
|
14
|
+
| `name` | ✗ | The name of image | `"Face Editor image"` |
|
|
15
|
+
|
|
8
16
|
#### Synchronous Client
|
|
9
17
|
|
|
10
18
|
```python
|
|
@@ -33,6 +41,7 @@ res = client.v1.ai_face_editor.create(
|
|
|
33
41
|
},
|
|
34
42
|
name="Face Editor image",
|
|
35
43
|
)
|
|
44
|
+
|
|
36
45
|
```
|
|
37
46
|
|
|
38
47
|
#### Asynchronous Client
|
|
@@ -63,4 +72,13 @@ res = await client.v1.ai_face_editor.create(
|
|
|
63
72
|
},
|
|
64
73
|
name="Face Editor image",
|
|
65
74
|
)
|
|
75
|
+
|
|
66
76
|
```
|
|
77
|
+
|
|
78
|
+
#### Response
|
|
79
|
+
|
|
80
|
+
##### Type
|
|
81
|
+
[V1AiFaceEditorCreateResponse](/magic_hour/types/models/v1_ai_face_editor_create_response.py)
|
|
82
|
+
|
|
83
|
+
##### Example
|
|
84
|
+
`{"credits_charged": 1, "frame_cost": 1, "id": "clx7uu86w0a5qp55yxz315r6r"}`
|
|
@@ -5,6 +5,13 @@ Create an AI GIF. Each GIF costs 25 credits.
|
|
|
5
5
|
|
|
6
6
|
**API Endpoint**: `POST /v1/ai-gif-generator`
|
|
7
7
|
|
|
8
|
+
#### Parameters
|
|
9
|
+
|
|
10
|
+
| Parameter | Required | Description | Example |
|
|
11
|
+
|-----------|:--------:|-------------|--------|
|
|
12
|
+
| `style` | ✓ | | `{"prompt": "Cute dancing cat, pixel art"}` |
|
|
13
|
+
| `name` | ✗ | The name of gif | `"Ai Gif gif"` |
|
|
14
|
+
|
|
8
15
|
#### Synchronous Client
|
|
9
16
|
|
|
10
17
|
```python
|
|
@@ -15,6 +22,7 @@ client = Client(token=getenv("API_TOKEN"))
|
|
|
15
22
|
res = client.v1.ai_gif_generator.create(
|
|
16
23
|
style={"prompt": "Cute dancing cat, pixel art"}, name="Ai Gif gif"
|
|
17
24
|
)
|
|
25
|
+
|
|
18
26
|
```
|
|
19
27
|
|
|
20
28
|
#### Asynchronous Client
|
|
@@ -27,4 +35,13 @@ client = AsyncClient(token=getenv("API_TOKEN"))
|
|
|
27
35
|
res = await client.v1.ai_gif_generator.create(
|
|
28
36
|
style={"prompt": "Cute dancing cat, pixel art"}, name="Ai Gif gif"
|
|
29
37
|
)
|
|
38
|
+
|
|
30
39
|
```
|
|
40
|
+
|
|
41
|
+
#### Response
|
|
42
|
+
|
|
43
|
+
##### Type
|
|
44
|
+
[V1AiGifGeneratorCreateResponse](/magic_hour/types/models/v1_ai_gif_generator_create_response.py)
|
|
45
|
+
|
|
46
|
+
##### Example
|
|
47
|
+
`{"credits_charged": 25, "frame_cost": 25, "id": "clx7uu86w0a5qp55yxz315r6r"}`
|
|
@@ -5,6 +5,14 @@ Create an AI headshot. Each headshot costs 50 credits.
|
|
|
5
5
|
|
|
6
6
|
**API Endpoint**: `POST /v1/ai-headshot-generator`
|
|
7
7
|
|
|
8
|
+
#### Parameters
|
|
9
|
+
|
|
10
|
+
| Parameter | Required | Description | Example |
|
|
11
|
+
|-----------|:--------:|-------------|--------|
|
|
12
|
+
| `assets` | ✓ | Provide the assets for headshot photo | `{"image_file_path": "api-assets/id/1234.png"}` |
|
|
13
|
+
| `name` | ✗ | The name of image | `"Ai Headshot image"` |
|
|
14
|
+
| `style` | ✗ | | `{"prompt": "professional passport photo, business attire, smiling, good posture, light blue background, centered, plain background"}` |
|
|
15
|
+
|
|
8
16
|
#### Synchronous Client
|
|
9
17
|
|
|
10
18
|
```python
|
|
@@ -15,6 +23,7 @@ client = Client(token=getenv("API_TOKEN"))
|
|
|
15
23
|
res = client.v1.ai_headshot_generator.create(
|
|
16
24
|
assets={"image_file_path": "api-assets/id/1234.png"}, name="Ai Headshot image"
|
|
17
25
|
)
|
|
26
|
+
|
|
18
27
|
```
|
|
19
28
|
|
|
20
29
|
#### Asynchronous Client
|
|
@@ -27,4 +36,13 @@ client = AsyncClient(token=getenv("API_TOKEN"))
|
|
|
27
36
|
res = await client.v1.ai_headshot_generator.create(
|
|
28
37
|
assets={"image_file_path": "api-assets/id/1234.png"}, name="Ai Headshot image"
|
|
29
38
|
)
|
|
39
|
+
|
|
30
40
|
```
|
|
41
|
+
|
|
42
|
+
#### Response
|
|
43
|
+
|
|
44
|
+
##### Type
|
|
45
|
+
[V1AiHeadshotGeneratorCreateResponse](/magic_hour/types/models/v1_ai_headshot_generator_create_response.py)
|
|
46
|
+
|
|
47
|
+
##### Example
|
|
48
|
+
`{"credits_charged": 50, "frame_cost": 50, "id": "clx7uu86w0a5qp55yxz315r6r"}`
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
|
|
2
|
+
### AI Image Editor <a name="create"></a>
|
|
3
|
+
|
|
4
|
+
Edit images with AI. Each edit costs 50 credits.
|
|
5
|
+
|
|
6
|
+
**API Endpoint**: `POST /v1/ai-image-editor`
|
|
7
|
+
|
|
8
|
+
#### Parameters
|
|
9
|
+
|
|
10
|
+
| Parameter | Required | Description | Example |
|
|
11
|
+
|-----------|:--------:|-------------|--------|
|
|
12
|
+
| `assets` | ✓ | Provide the assets for image edit | `{"image_file_path": "api-assets/id/1234.png"}` |
|
|
13
|
+
| `style` | ✓ | | `{"prompt": "Give me sunglasses"}` |
|
|
14
|
+
| `name` | ✗ | The name of image | `"Ai Image Editor image"` |
|
|
15
|
+
|
|
16
|
+
#### Synchronous Client
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from magic_hour import Client
|
|
20
|
+
from os import getenv
|
|
21
|
+
|
|
22
|
+
client = Client(token=getenv("API_TOKEN"))
|
|
23
|
+
res = client.v1.ai_image_editor.create(
|
|
24
|
+
assets={"image_file_path": "api-assets/id/1234.png"},
|
|
25
|
+
style={"prompt": "Give me sunglasses"},
|
|
26
|
+
name="Ai Image Editor image",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
#### Asynchronous Client
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from magic_hour import AsyncClient
|
|
35
|
+
from os import getenv
|
|
36
|
+
|
|
37
|
+
client = AsyncClient(token=getenv("API_TOKEN"))
|
|
38
|
+
res = await client.v1.ai_image_editor.create(
|
|
39
|
+
assets={"image_file_path": "api-assets/id/1234.png"},
|
|
40
|
+
style={"prompt": "Give me sunglasses"},
|
|
41
|
+
name="Ai Image Editor image",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
#### Response
|
|
47
|
+
|
|
48
|
+
##### Type
|
|
49
|
+
[V1AiImageEditorCreateResponse](/magic_hour/types/models/v1_ai_image_editor_create_response.py)
|
|
50
|
+
|
|
51
|
+
##### Example
|
|
52
|
+
`{"credits_charged": 50, "frame_cost": 50, "id": "clx7uu86w0a5qp55yxz315r6r"}`
|