magic_hour 0.8.1__py3-none-any.whl → 0.8.3__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 CHANGED
@@ -1,9 +1,9 @@
1
1
  import httpx
2
2
  import typing
3
3
 
4
- from magic_hour.core import AsyncBaseClient, AuthBearer, SyncBaseClient
5
- from magic_hour.environment import Environment
6
4
  from magic_hour.resources.v1 import AsyncV1Client, V1Client
5
+ from magic_hour.environment import Environment
6
+ from magic_hour.core import AsyncBaseClient, AuthBearer, SyncBaseClient
7
7
 
8
8
 
9
9
  class Client:
@@ -22,9 +22,9 @@ class Client:
22
22
  httpx.Client(timeout=timeout) if httpx_client is None else httpx_client
23
23
  ),
24
24
  )
25
+ self._base_client.register_auth("bearerAuth", AuthBearer(val=token))
25
26
 
26
27
  self.v1 = V1Client(base_client=self._base_client)
27
- self._base_client.register_auth("bearerAuth", AuthBearer(val=token))
28
28
 
29
29
 
30
30
  class AsyncClient:
@@ -45,9 +45,9 @@ class AsyncClient:
45
45
  else httpx_client
46
46
  ),
47
47
  )
48
+ self._base_client.register_auth("bearerAuth", AuthBearer(val=token))
48
49
 
49
50
  self.v1 = AsyncV1Client(base_client=self._base_client)
50
- self._base_client.register_auth("bearerAuth", AuthBearer(val=token))
51
51
 
52
52
 
53
53
  def _get_base_url(
@@ -1,4 +1,10 @@
1
+ """
2
+ Generated by Sideko (sideko.dev)
3
+ """
4
+
5
+ from json import JSONDecodeError
1
6
  import typing
7
+ import httpx
2
8
 
3
9
 
4
10
  class ApiError(Exception):
@@ -13,29 +19,31 @@ class ApiError(Exception):
13
19
  None if no status code is applicable.
14
20
  body: The response body or error message content.
15
21
  Can be any type depending on the API response format.
22
+ response: The raw httpx response object. See https://www.python-httpx.org/api/#response for object reference
16
23
  """
17
24
 
18
25
  status_code: typing.Optional[int]
19
26
  body: typing.Any
27
+ response: httpx.Response
20
28
 
21
- def __init__(
22
- self, *, status_code: typing.Optional[int] = None, body: typing.Any = None
23
- ) -> None:
29
+ def __init__(self, *, response: httpx.Response) -> None:
24
30
  """
25
31
  Initialize the ApiError with optional status code and body.
26
32
 
27
33
  Args:
28
34
  status_code: The HTTP status code of the error.
29
35
  Defaults to None.
30
- body: The response body or error message content.
31
- Defaults to None.
32
36
 
33
37
  Note:
34
38
  The asterisk (*) in the parameters forces keyword arguments,
35
39
  making the instantiation more explicit.
36
40
  """
37
- self.status_code = status_code
38
- self.body = body
41
+ try:
42
+ self.body = response.json()
43
+ except JSONDecodeError:
44
+ self.body = None
45
+ self.status_code = response.status_code
46
+ self.response = response
39
47
 
40
48
  def __str__(self) -> str:
41
49
  """
@@ -1,4 +1,3 @@
1
- from json import JSONDecodeError
2
1
  from typing import (
3
2
  Any,
4
3
  List,
@@ -9,6 +8,7 @@ from typing import (
9
8
  Union,
10
9
  cast,
11
10
  )
11
+ from typing_extensions import TypeGuard
12
12
 
13
13
  import httpx
14
14
  from pydantic import BaseModel
@@ -17,7 +17,7 @@ from .api_error import ApiError
17
17
  from .auth import AuthProvider
18
18
  from .request import RequestConfig, RequestOptions, default_request_options, QueryParams
19
19
  from .response import from_encodable, AsyncStreamResponse, StreamResponse
20
- from .utils import is_binary_content_type, get_content_type
20
+ from .utils import get_response_type, filter_binary_response
21
21
  from .binary_response import BinaryResponse
22
22
 
23
23
  NoneType = type(None)
@@ -96,6 +96,15 @@ class BaseClient:
96
96
 
97
97
  return f"{base}/{path}"
98
98
 
99
+ def _cast_to_raw_response(
100
+ self, res: httpx.Response, cast_to: Union[Type[T], Any]
101
+ ) -> TypeGuard[T]:
102
+ """Determines if the provided cast_to is an httpx.Response"""
103
+ try:
104
+ return issubclass(cast_to, httpx.Response)
105
+ except TypeError:
106
+ return False
107
+
99
108
  def _apply_auth(
100
109
  self, *, cfg: RequestConfig, auth_names: List[str]
101
110
  ) -> RequestConfig:
@@ -307,35 +316,30 @@ class BaseClient:
307
316
  Raises:
308
317
  ApiError: If the response indicates an error
309
318
  """
310
- if 200 <= response.status_code < 300:
311
- content_type = get_content_type(response.headers)
312
- if response.status_code == 204 or cast_to == NoneType:
313
- return cast(T, None)
314
- elif cast_to == BinaryResponse:
315
- return cast(
316
- T,
317
- BinaryResponse(content=response.content, headers=response.headers),
318
- )
319
- else:
320
- if "json" in content_type or "form" in content_type:
321
- if cast_to is type(Any):
322
- return response.json()
323
- return from_encodable(data=response.json(), load_with=cast_to)
324
- elif is_binary_content_type(content_type):
325
- return cast(
326
- T,
327
- BinaryResponse(
328
- content=response.content, headers=response.headers
329
- ),
330
- )
331
- else:
332
- return from_encodable(data=response.content, load_with=cast_to)
319
+
320
+ if response.status_code == 204 or cast_to == NoneType:
321
+ return cast(T, None)
322
+ elif cast_to == BinaryResponse:
323
+ return cast(
324
+ T,
325
+ BinaryResponse(content=response.content, headers=response.headers),
326
+ )
327
+
328
+ response_type = get_response_type(response.headers)
329
+
330
+ if response_type == "json":
331
+ if cast_to is type(Any):
332
+ return response.json()
333
+ return from_encodable(
334
+ data=response.json(), load_with=filter_binary_response(cast_to=cast_to)
335
+ )
336
+ elif response_type == "text":
337
+ return cast(T, response.text)
333
338
  else:
334
- try:
335
- response_json = response.json()
336
- except JSONDecodeError:
337
- raise ApiError(status_code=response.status_code, body=response.text)
338
- raise ApiError(status_code=response.status_code, body=response_json)
339
+ return cast(
340
+ T,
341
+ BinaryResponse(content=response.content, headers=response.headers),
342
+ )
339
343
 
340
344
 
341
345
  class SyncBaseClient(BaseClient):
@@ -411,6 +415,13 @@ class SyncBaseClient(BaseClient):
411
415
  request_options=request_options,
412
416
  )
413
417
  response = self.httpx_client.request(**req_cfg)
418
+
419
+ if not response.is_success:
420
+ raise ApiError(response=response)
421
+
422
+ if self._cast_to_raw_response(res=response, cast_to=cast_to):
423
+ return response
424
+
414
425
  return self.process_response(response=response, cast_to=cast_to)
415
426
 
416
427
  def stream_request(
@@ -542,6 +553,13 @@ class AsyncBaseClient(BaseClient):
542
553
  request_options=request_options,
543
554
  )
544
555
  response = await self.httpx_client.request(**req_cfg)
556
+
557
+ if not response.is_success:
558
+ raise ApiError(response=response)
559
+
560
+ if self._cast_to_raw_response(res=response, cast_to=cast_to):
561
+ return response
562
+
545
563
  return self.process_response(response=response, cast_to=cast_to)
546
564
 
547
565
  async def stream_request(
magic_hour/core/utils.py CHANGED
@@ -1,6 +1,11 @@
1
1
  import typing
2
+ import re
3
+ from typing_extensions import Literal
4
+
2
5
  import httpx
3
6
 
7
+ from .binary_response import BinaryResponse
8
+
4
9
 
5
10
  def remove_none_from_dict(
6
11
  original: typing.Dict[str, typing.Optional[typing.Any]],
@@ -12,27 +17,39 @@ def remove_none_from_dict(
12
17
  return new
13
18
 
14
19
 
15
- def is_binary_content_type(content_type: str) -> bool:
16
- """Check if the content type indicates binary data."""
17
- binary_types = [
18
- "application/octet-stream",
19
- "application/pdf",
20
- "application/zip",
21
- "image/",
22
- "audio/",
23
- "video/",
24
- "application/msword",
25
- "application/vnd.openxmlformats-officedocument",
26
- "application/x-binary",
27
- "application/vnd.ms-excel",
28
- "application/vnd.ms-powerpoint",
29
- ]
30
- return any(binary_type in content_type for binary_type in binary_types)
31
-
32
-
33
- def get_content_type(headers: httpx.Headers) -> str:
34
- """Get content type in a case-insensitive manner."""
35
- for key, value in headers.items():
36
- if key.lower() == "content-type":
37
- return value.lower()
38
- return ""
20
+ def get_response_type(headers: httpx.Headers) -> Literal["json", "text", "binary"]:
21
+ """Check response type based on content type"""
22
+ content_type = headers.get("content-type")
23
+
24
+ if re.search("^application/(.+\+)?json", content_type):
25
+ return "json"
26
+ elif re.search("^text/(.+)", content_type):
27
+ return "text"
28
+ else:
29
+ return "binary"
30
+
31
+
32
+ def is_union_type(type_hint: typing.Any) -> bool:
33
+ """Check if a type hint is a Union type."""
34
+ return hasattr(type_hint, "__origin__") and type_hint.__origin__ is typing.Union
35
+
36
+
37
+ def filter_binary_response(cast_to: typing.Type) -> typing.Type:
38
+ """
39
+ Filters out BinaryResponse from a Union type.
40
+ If cast_to is not a Union, returns it unchanged.
41
+ """
42
+ if not is_union_type(cast_to):
43
+ return cast_to
44
+
45
+ types = typing.get_args(cast_to)
46
+ filtered = tuple(t for t in types if t != BinaryResponse)
47
+
48
+ # If everything was filtered out, return original type
49
+ if not filtered:
50
+ return cast_to
51
+ # If only one type remains, return it directly
52
+ if len(filtered) == 1:
53
+ return typing.cast(typing.Type, filtered[0])
54
+ # Otherwise return new Union with filtered types
55
+ return typing.cast(typing.Type, typing.Union[filtered]) # type: ignore
magic_hour/environment.py CHANGED
@@ -3,4 +3,4 @@ import enum
3
3
 
4
4
  class Environment(enum.Enum):
5
5
  ENVIRONMENT = "https://api.magichour.ai"
6
- MOCK_SERVER = "https://api.sideko.dev/v1/mock/magichour/magic-hour/0.8.1"
6
+ MOCK_SERVER = "https://api.sideko.dev/v1/mock/magichour/magic-hour/0.8.3"
@@ -19,8 +19,8 @@ res = client.v1.animation.create(
19
19
  "audio_source": "file",
20
20
  "image_file_path": "api-assets/id/1234.png",
21
21
  },
22
- end_seconds=15,
23
- fps=12,
22
+ end_seconds=15.0,
23
+ fps=12.0,
24
24
  height=960,
25
25
  style={
26
26
  "art_style": "Painterly Illustration",
@@ -47,8 +47,8 @@ res = await client.v1.animation.create(
47
47
  "audio_source": "file",
48
48
  "image_file_path": "api-assets/id/1234.png",
49
49
  },
50
- end_seconds=15,
51
- fps=12,
50
+ end_seconds=15.0,
51
+ fps=12.0,
52
52
  height=960,
53
53
  style={
54
54
  "art_style": "Painterly Illustration",
@@ -57,8 +57,8 @@ class AnimationClient:
57
57
  ```py
58
58
  client.v1.animation.create(
59
59
  assets={"audio_source": "file"},
60
- end_seconds=15,
61
- fps=12,
60
+ end_seconds=15.0,
61
+ fps=12.0,
62
62
  height=960,
63
63
  style={
64
64
  "art_style": "Painterly Illustration",
@@ -141,8 +141,8 @@ class AsyncAnimationClient:
141
141
  ```py
142
142
  await client.v1.animation.create(
143
143
  assets={"audio_source": "file"},
144
- end_seconds=15,
145
- fps=12,
144
+ end_seconds=15.0,
145
+ fps=12.0,
146
146
  height=960,
147
147
  style={
148
148
  "art_style": "Painterly Illustration",
@@ -22,9 +22,9 @@ res = client.v1.face_swap.create(
22
22
  "video_file_path": "video/id/1234.mp4",
23
23
  "video_source": "file",
24
24
  },
25
- end_seconds=15,
25
+ end_seconds=15.0,
26
26
  height=960,
27
- start_seconds=0,
27
+ start_seconds=0.0,
28
28
  width=512,
29
29
  name="Face Swap video",
30
30
  )
@@ -43,9 +43,9 @@ res = await client.v1.face_swap.create(
43
43
  "video_file_path": "video/id/1234.mp4",
44
44
  "video_source": "file",
45
45
  },
46
- end_seconds=15,
46
+ end_seconds=15.0,
47
47
  height=960,
48
- start_seconds=0,
48
+ start_seconds=0.0,
49
49
  width=512,
50
50
  name="Face Swap video",
51
51
  )
@@ -58,9 +58,9 @@ class FaceSwapClient:
58
58
  ```py
59
59
  client.v1.face_swap.create(
60
60
  assets={"image_file_path": "image/id/1234.png", "video_source": "file"},
61
- end_seconds=15,
61
+ end_seconds=15.0,
62
62
  height=960,
63
- start_seconds=0,
63
+ start_seconds=0.0,
64
64
  width=512,
65
65
  name="Face Swap video",
66
66
  )
@@ -135,9 +135,9 @@ class AsyncFaceSwapClient:
135
135
  ```py
136
136
  await client.v1.face_swap.create(
137
137
  assets={"image_file_path": "image/id/1234.png", "video_source": "file"},
138
- end_seconds=15,
138
+ end_seconds=15.0,
139
139
  height=960,
140
- start_seconds=0,
140
+ start_seconds=0.0,
141
141
  width=512,
142
142
  name="Face Swap video",
143
143
  )
@@ -13,7 +13,7 @@ from magic_hour import Client
13
13
  from os import getenv
14
14
 
15
15
  client = Client(token=getenv("API_TOKEN"))
16
- res = client.v1.image_projects.delete(id="string")
16
+ res = client.v1.image_projects.delete(id="cm6pvghix03bvyz0zwash6noj")
17
17
  ```
18
18
 
19
19
  #### Asynchronous Client
@@ -23,7 +23,7 @@ from magic_hour import AsyncClient
23
23
  from os import getenv
24
24
 
25
25
  client = AsyncClient(token=getenv("API_TOKEN"))
26
- res = await client.v1.image_projects.delete(id="string")
26
+ res = await client.v1.image_projects.delete(id="cm6pvghix03bvyz0zwash6noj")
27
27
  ```
28
28
 
29
29
  ### get <a name="get"></a>
@@ -49,7 +49,7 @@ from magic_hour import Client
49
49
  from os import getenv
50
50
 
51
51
  client = Client(token=getenv("API_TOKEN"))
52
- res = client.v1.image_projects.get(id="string")
52
+ res = client.v1.image_projects.get(id="cm6pvghix03bvyz0zwash6noj")
53
53
  ```
54
54
 
55
55
  #### Asynchronous Client
@@ -59,5 +59,5 @@ from magic_hour import AsyncClient
59
59
  from os import getenv
60
60
 
61
61
  client = AsyncClient(token=getenv("API_TOKEN"))
62
- res = await client.v1.image_projects.get(id="string")
62
+ res = await client.v1.image_projects.get(id="cm6pvghix03bvyz0zwash6noj")
63
63
  ```
@@ -36,7 +36,7 @@ class ImageProjectsClient:
36
36
 
37
37
  Examples:
38
38
  ```py
39
- client.v1.image_projects.delete(id="string")
39
+ client.v1.image_projects.delete(id="cm6pvghix03bvyz0zwash6noj")
40
40
  ```
41
41
 
42
42
  """
@@ -80,7 +80,7 @@ class ImageProjectsClient:
80
80
 
81
81
  Examples:
82
82
  ```py
83
- client.v1.image_projects.get(id="string")
83
+ client.v1.image_projects.get(id="cm6pvghix03bvyz0zwash6noj")
84
84
  ```
85
85
 
86
86
  """
@@ -120,7 +120,7 @@ class AsyncImageProjectsClient:
120
120
 
121
121
  Examples:
122
122
  ```py
123
- await client.v1.image_projects.delete(id="string")
123
+ await client.v1.image_projects.delete(id="cm6pvghix03bvyz0zwash6noj")
124
124
  ```
125
125
 
126
126
  """
@@ -164,7 +164,7 @@ class AsyncImageProjectsClient:
164
164
 
165
165
  Examples:
166
166
  ```py
167
- await client.v1.image_projects.get(id="string")
167
+ await client.v1.image_projects.get(id="cm6pvghix03bvyz0zwash6noj")
168
168
  ```
169
169
 
170
170
  """
@@ -18,7 +18,7 @@ from os import getenv
18
18
  client = Client(token=getenv("API_TOKEN"))
19
19
  res = client.v1.image_to_video.create(
20
20
  assets={"image_file_path": "image/id/1234.png"},
21
- end_seconds=5,
21
+ end_seconds=5.0,
22
22
  height=960,
23
23
  style={"prompt": None},
24
24
  width=512,
@@ -35,7 +35,7 @@ from os import getenv
35
35
  client = AsyncClient(token=getenv("API_TOKEN"))
36
36
  res = await client.v1.image_to_video.create(
37
37
  assets={"image_file_path": "image/id/1234.png"},
38
- end_seconds=5,
38
+ end_seconds=5.0,
39
39
  height=960,
40
40
  style={"prompt": None},
41
41
  width=512,
@@ -58,7 +58,7 @@ class ImageToVideoClient:
58
58
  ```py
59
59
  client.v1.image_to_video.create(
60
60
  assets={"image_file_path": "image/id/1234.png"},
61
- end_seconds=5,
61
+ end_seconds=5.0,
62
62
  height=960,
63
63
  style={"prompt": "string"},
64
64
  width=512,
@@ -135,7 +135,7 @@ class AsyncImageToVideoClient:
135
135
  ```py
136
136
  await client.v1.image_to_video.create(
137
137
  assets={"image_file_path": "image/id/1234.png"},
138
- end_seconds=5,
138
+ end_seconds=5.0,
139
139
  height=960,
140
140
  style={"prompt": "string"},
141
141
  width=512,
@@ -22,11 +22,11 @@ res = client.v1.lip_sync.create(
22
22
  "video_file_path": "video/id/1234.mp4",
23
23
  "video_source": "file",
24
24
  },
25
- end_seconds=15,
25
+ end_seconds=15.0,
26
26
  height=960,
27
- start_seconds=0,
27
+ start_seconds=0.0,
28
28
  width=512,
29
- max_fps_limit=12,
29
+ max_fps_limit=12.0,
30
30
  name="Lip Sync video",
31
31
  )
32
32
  ```
@@ -44,11 +44,11 @@ res = await client.v1.lip_sync.create(
44
44
  "video_file_path": "video/id/1234.mp4",
45
45
  "video_source": "file",
46
46
  },
47
- end_seconds=15,
47
+ end_seconds=15.0,
48
48
  height=960,
49
- start_seconds=0,
49
+ start_seconds=0.0,
50
50
  width=512,
51
- max_fps_limit=12,
51
+ max_fps_limit=12.0,
52
52
  name="Lip Sync video",
53
53
  )
54
54
  ```
@@ -62,11 +62,11 @@ class LipSyncClient:
62
62
  ```py
63
63
  client.v1.lip_sync.create(
64
64
  assets={"audio_file_path": "audio/id/1234.mp3", "video_source": "file"},
65
- end_seconds=15,
65
+ end_seconds=15.0,
66
66
  height=960,
67
- start_seconds=0,
67
+ start_seconds=0.0,
68
68
  width=512,
69
- max_fps_limit=12,
69
+ max_fps_limit=12.0,
70
70
  name="Lip Sync video",
71
71
  )
72
72
  ```
@@ -145,11 +145,11 @@ class AsyncLipSyncClient:
145
145
  ```py
146
146
  await client.v1.lip_sync.create(
147
147
  assets={"audio_file_path": "audio/id/1234.mp3", "video_source": "file"},
148
- end_seconds=15,
148
+ end_seconds=15.0,
149
149
  height=960,
150
- start_seconds=0,
150
+ start_seconds=0.0,
151
151
  width=512,
152
- max_fps_limit=12,
152
+ max_fps_limit=12.0,
153
153
  name="Lip Sync video",
154
154
  )
155
155
  ```
@@ -17,7 +17,7 @@ from os import getenv
17
17
 
18
18
  client = Client(token=getenv("API_TOKEN"))
19
19
  res = client.v1.text_to_video.create(
20
- end_seconds=5,
20
+ end_seconds=5.0,
21
21
  orientation="landscape",
22
22
  style={"prompt": "string"},
23
23
  name="Text To Video video",
@@ -32,7 +32,7 @@ from os import getenv
32
32
 
33
33
  client = AsyncClient(token=getenv("API_TOKEN"))
34
34
  res = await client.v1.text_to_video.create(
35
- end_seconds=5,
35
+ end_seconds=5.0,
36
36
  orientation="landscape",
37
37
  style={"prompt": "string"},
38
38
  name="Text To Video video",
@@ -54,7 +54,7 @@ class TextToVideoClient:
54
54
  Examples:
55
55
  ```py
56
56
  client.v1.text_to_video.create(
57
- end_seconds=5,
57
+ end_seconds=5.0,
58
58
  orientation="landscape",
59
59
  style={"prompt": "string"},
60
60
  name="Text To Video video",
@@ -123,7 +123,7 @@ class AsyncTextToVideoClient:
123
123
  Examples:
124
124
  ```py
125
125
  await client.v1.text_to_video.create(
126
- end_seconds=5,
126
+ end_seconds=5.0,
127
127
  orientation="landscape",
128
128
  style={"prompt": "string"},
129
129
  name="Text To Video video",
@@ -13,7 +13,7 @@ from magic_hour import Client
13
13
  from os import getenv
14
14
 
15
15
  client = Client(token=getenv("API_TOKEN"))
16
- res = client.v1.video_projects.delete(id="string")
16
+ res = client.v1.video_projects.delete(id="cm6pvghix03bvyz0zwash6noj")
17
17
  ```
18
18
 
19
19
  #### Asynchronous Client
@@ -23,7 +23,7 @@ from magic_hour import AsyncClient
23
23
  from os import getenv
24
24
 
25
25
  client = AsyncClient(token=getenv("API_TOKEN"))
26
- res = await client.v1.video_projects.delete(id="string")
26
+ res = await client.v1.video_projects.delete(id="cm6pvghix03bvyz0zwash6noj")
27
27
  ```
28
28
 
29
29
  ### get <a name="get"></a>
@@ -49,7 +49,7 @@ from magic_hour import Client
49
49
  from os import getenv
50
50
 
51
51
  client = Client(token=getenv("API_TOKEN"))
52
- res = client.v1.video_projects.get(id="string")
52
+ res = client.v1.video_projects.get(id="cm6pvghix03bvyz0zwash6noj")
53
53
  ```
54
54
 
55
55
  #### Asynchronous Client
@@ -59,5 +59,5 @@ from magic_hour import AsyncClient
59
59
  from os import getenv
60
60
 
61
61
  client = AsyncClient(token=getenv("API_TOKEN"))
62
- res = await client.v1.video_projects.get(id="string")
62
+ res = await client.v1.video_projects.get(id="cm6pvghix03bvyz0zwash6noj")
63
63
  ```
@@ -36,7 +36,7 @@ class VideoProjectsClient:
36
36
 
37
37
  Examples:
38
38
  ```py
39
- client.v1.video_projects.delete(id="string")
39
+ client.v1.video_projects.delete(id="cm6pvghix03bvyz0zwash6noj")
40
40
  ```
41
41
 
42
42
  """
@@ -80,7 +80,7 @@ class VideoProjectsClient:
80
80
 
81
81
  Examples:
82
82
  ```py
83
- client.v1.video_projects.get(id="string")
83
+ client.v1.video_projects.get(id="cm6pvghix03bvyz0zwash6noj")
84
84
  ```
85
85
 
86
86
  """
@@ -120,7 +120,7 @@ class AsyncVideoProjectsClient:
120
120
 
121
121
  Examples:
122
122
  ```py
123
- await client.v1.video_projects.delete(id="string")
123
+ await client.v1.video_projects.delete(id="cm6pvghix03bvyz0zwash6noj")
124
124
  ```
125
125
 
126
126
  """
@@ -164,7 +164,7 @@ class AsyncVideoProjectsClient:
164
164
 
165
165
  Examples:
166
166
  ```py
167
- await client.v1.video_projects.get(id="string")
167
+ await client.v1.video_projects.get(id="cm6pvghix03bvyz0zwash6noj")
168
168
  ```
169
169
 
170
170
  """
@@ -18,9 +18,9 @@ from os import getenv
18
18
  client = Client(token=getenv("API_TOKEN"))
19
19
  res = client.v1.video_to_video.create(
20
20
  assets={"video_file_path": "video/id/1234.mp4", "video_source": "file"},
21
- end_seconds=15,
21
+ end_seconds=15.0,
22
22
  height=960,
23
- start_seconds=0,
23
+ start_seconds=0.0,
24
24
  style={
25
25
  "art_style": "3D Render",
26
26
  "model": "Absolute Reality",
@@ -43,9 +43,9 @@ from os import getenv
43
43
  client = AsyncClient(token=getenv("API_TOKEN"))
44
44
  res = await client.v1.video_to_video.create(
45
45
  assets={"video_file_path": "video/id/1234.mp4", "video_source": "file"},
46
- end_seconds=15,
46
+ end_seconds=15.0,
47
47
  height=960,
48
- start_seconds=0,
48
+ start_seconds=0.0,
49
49
  style={
50
50
  "art_style": "3D Render",
51
51
  "model": "Absolute Reality",
@@ -68,9 +68,9 @@ class VideoToVideoClient:
68
68
  ```py
69
69
  client.v1.video_to_video.create(
70
70
  assets={"video_source": "file"},
71
- end_seconds=15,
71
+ end_seconds=15.0,
72
72
  height=960,
73
- start_seconds=0,
73
+ start_seconds=0.0,
74
74
  style={
75
75
  "art_style": "3D Render",
76
76
  "model": "Absolute Reality",
@@ -164,9 +164,9 @@ class AsyncVideoToVideoClient:
164
164
  ```py
165
165
  await client.v1.video_to_video.create(
166
166
  assets={"video_source": "file"},
167
- end_seconds=15,
167
+ end_seconds=15.0,
168
168
  height=960,
169
- start_seconds=0,
169
+ start_seconds=0.0,
170
170
  style={
171
171
  "art_style": "3D Render",
172
172
  "model": "Absolute Reality",
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: magic_hour
3
- Version: 0.8.1
4
- Summary:
3
+ Version: 0.8.3
4
+ Summary: Python SDK for Magic Hour API
5
5
  Requires-Python: >=3.8,<4.0
6
6
  Classifier: Programming Language :: Python :: 3
7
7
  Classifier: Programming Language :: Python :: 3.8
@@ -16,52 +16,47 @@ Requires-Dist: pydantic (>=2.5.0,<3.0.0)
16
16
  Requires-Dist: typing_extensions (>=4.0.0,<5.0.0)
17
17
  Description-Content-Type: text/markdown
18
18
 
19
+ # Magic Hour Python SDK
19
20
 
20
- # Magic Hour API Python SDK
21
+ [![PyPI - Version](https://img.shields.io/pypi/v/magic_hour)](https://pypi.org/project/magic_hour/)
21
22
 
22
- ## Overview
23
-
24
- # Introduction
25
-
26
- Magic Hour provides an API (beta) that can be integrated into your own application to generate videos using AI.
23
+ Magic Hour provides an API (beta) that can be integrated into your own application to generate videos and images using AI.
27
24
 
28
25
  Webhook documentation can be found [here](https://magichour.ai/docs/webhook).
29
26
 
30
27
  If you have any questions, please reach out to us via [discord](https://discord.gg/JX5rgsZaJp).
31
28
 
32
- # Authentication
33
-
34
- Every request requires an API key.
35
-
36
- To get started, first generate your API key [here](https://magichour.ai/settings/developer).
29
+ ## Install
37
30
 
38
- Then, add the `Authorization` header to the request.
39
-
40
- | Key | Value |
41
- |-|-|
42
- | Authorization | Bearer mhk_live_apikey |
31
+ ```
32
+ pip install magic_hour
33
+ ```
43
34
 
44
- > **Warning**: any API call that renders a video will utilize frames in your account.
35
+ ## Usage
45
36
 
37
+ Initialize the client
46
38
 
47
39
  ### Synchronous Client
48
40
 
49
41
  ```python
50
42
  from magic_hour import Client
51
- from os import getenv
52
43
 
53
- client = Client(token=getenv("API_TOKEN"))
44
+ # generate your API Key at https://magichour.ai/developer
45
+ client = Client(token="my api key")
54
46
  ```
55
47
 
56
48
  ### Asynchronous Client
57
49
 
58
50
  ```python
59
51
  from magic_hour import AsyncClient
60
- from os import getenv
61
52
 
62
- client = AsyncClient(token=getenv("API_TOKEN"))
53
+ # generate your API Key at https://magichour.ai/developer
54
+ client = AsyncClient(token="my api key")
63
55
  ```
64
56
 
57
+ > [!WARNING]
58
+ > Any API call that renders a video will utilize frames in your account.
59
+
65
60
  ## Module Documentation and Snippets
66
61
 
67
62
  ### [v1.ai_clothes_changer](magic_hour/resources/v1/ai_clothes_changer/README.md)
@@ -1,15 +1,15 @@
1
1
  magic_hour/__init__.py,sha256=9mcpfN52bfc0cw9wEz7dXOVpOsUuGmVdqFQ79NdnGYU,203
2
- magic_hour/client.py,sha256=S2K_ze-tISm1m24G9XEa1Olg7FMgJqf81AZqhRb47w0,2014
2
+ magic_hour/client.py,sha256=2UhLa0mDgXsr5PQuPT5DUgwvXBVrwV1GskWUy5nKrr8,2014
3
3
  magic_hour/core/__init__.py,sha256=i6cIezufptVYpQS9F90nLCDIliQVXjUBsufIXmuI_9g,1145
4
- magic_hour/core/api_error.py,sha256=1ocVMiyV3LRwP7EGQtyG8SMmpxl_dRCADwJ44ncHbA4,1546
4
+ magic_hour/core/api_error.py,sha256=K1d47qRbhLBNEaUVbs0NPgxee24X3qGZ37gBhleUzEE,1760
5
5
  magic_hour/core/auth.py,sha256=NSjPcmTyHelRag9FqH1ufbchBWQgGDpH2P0akfppIPo,9130
6
- magic_hour/core/base_client.py,sha256=IiHdTuYAqq3CxRfsYcIMKthjLRVSSrOM15hSuc77DyE,18688
6
+ magic_hour/core/base_client.py,sha256=H9FJtNvN3ZfjjnPq4t9HXgh0c1324x1RFtAHXWEnqWk,18846
7
7
  magic_hour/core/binary_response.py,sha256=T-DATvb21P2ZRnYa4LlXmF77VfM8Tut2951QHEQct_U,681
8
8
  magic_hour/core/request.py,sha256=zdw-rie5wCy9MxIQ5ilhnZaBVPRyF8WAiZ5pGTam7Oc,5224
9
9
  magic_hour/core/response.py,sha256=Sl7nPL2axmz7em_6d9TkFSnQQKUpalWaVWbPPWoXJgM,10180
10
10
  magic_hour/core/type_utils.py,sha256=4bU9WXnMXJ6YTtuqOMiB8t6Xw0RlfVWJ-IDBONlqEtQ,461
11
- magic_hour/core/utils.py,sha256=wl_QTElIzKqDrB3nNuTFyUmJ17x9PiXAfWHssUtu4Ds,1108
12
- magic_hour/environment.py,sha256=GnaY1_XehrmKSDwLVxVNcss6PihV_2ODyJItRnqNVp4,167
11
+ magic_hour/core/utils.py,sha256=Cp0SH9nBsdL53kCJSCsHgoJoo0HuXYmxShhQgeHw0KQ,1668
12
+ magic_hour/environment.py,sha256=gwePWRvEN7uiSigUI3KemZat9I5A-lmpPAHFzWAe00c,167
13
13
  magic_hour/resources/v1/__init__.py,sha256=Aj0sjVcoijjQyieNBxv2_uewPYC2vO2UG-ehoBgCz5E,86
14
14
  magic_hour/resources/v1/ai_clothes_changer/README.md,sha256=KQTvbttct5GcdOJW3NG5gCsWF6G2qlwIoBjBd92TjUs,977
15
15
  magic_hour/resources/v1/ai_clothes_changer/__init__.py,sha256=6W_Y2HxG2sDOBiJyzngK3Q2S3xfQgpK-j8xFRmBAhbQ,142
@@ -29,13 +29,13 @@ magic_hour/resources/v1/ai_photo_editor/client.py,sha256=9b29HjZuEkZG0YAx-ejix4u
29
29
  magic_hour/resources/v1/ai_qr_code_generator/README.md,sha256=w6IE0mm_D-PnW-bDsIu9k8oPePekZ2TuW3gOFhi95_w,734
30
30
  magic_hour/resources/v1/ai_qr_code_generator/__init__.py,sha256=HnSTg7tB8M5LibZoCDRdE5Q71efmiqZIkNEve5SO1Mg,146
31
31
  magic_hour/resources/v1/ai_qr_code_generator/client.py,sha256=Kyzyb7TvPVC6AyQx6ZFV8O_CcEYkrKr1NeCpDf3qOgc,3831
32
- magic_hour/resources/v1/animation/README.md,sha256=l2l1N8MVzXIAF6PqTblL5BqK6PHkldhoFPPdM3B0twY,1469
32
+ magic_hour/resources/v1/animation/README.md,sha256=uIVfUwD7iAOe2eJDgrxj4UyYmq9R30fdI3Z0JuEChc4,1477
33
33
  magic_hour/resources/v1/animation/__init__.py,sha256=M6KUe6TEZl_DAdyn1HFQ2kHYanZo6xy3mvUdCN264hQ,114
34
- magic_hour/resources/v1/animation/client.py,sha256=ZL2Hn2fjvnzdedZb2WapBwU-SYo351ib2bAhNt83qNM,6069
34
+ magic_hour/resources/v1/animation/client.py,sha256=R5JaUbZKFkFgSVpa2sSwvtNF5HR91AKJ1PyDbTGTK3g,6077
35
35
  magic_hour/resources/v1/client.py,sha256=A2aXAjY8Y2yESfS1l2OjvvmkujCC1yj2d9xzmFcvPm8,5213
36
- magic_hour/resources/v1/face_swap/README.md,sha256=HxUhZO-sUmKd4oAd4AyKESFauzCj1rmMD5_tKmEp-2k,1288
36
+ magic_hour/resources/v1/face_swap/README.md,sha256=jJTKHBhevcAZ9IeeVwW6OGatMdrDNVZvtQOiyMGqxL8,1296
37
37
  magic_hour/resources/v1/face_swap/__init__.py,sha256=lyg5uAHyYHEUVAiAZtP3zwjGCEGqq8IWbQKexVdhr00,110
38
- magic_hour/resources/v1/face_swap/client.py,sha256=_5D_DxqTugRdR1Z7bIZWX8cZQ0sny4uknzGv7v7iOj4,6145
38
+ magic_hour/resources/v1/face_swap/client.py,sha256=vNdhH4HQa_FSlk_18tNAVOqkqop9hX6Kb-uee-I_obQ,6153
39
39
  magic_hour/resources/v1/face_swap_photo/README.md,sha256=9iGINuGkWn60ZaZgZ4xz0Iho0lvfE-e_YVEA2vId6QU,964
40
40
  magic_hour/resources/v1/face_swap_photo/__init__.py,sha256=NZEplYX5kDPL_0qY0Q5tuxhDevipN0otByTYKMmF_1k,130
41
41
  magic_hour/resources/v1/face_swap_photo/client.py,sha256=w_Axxc58-EyvMYzzv4xLHNZyXfctMdGg_CeFUZSIgx0,4013
@@ -47,24 +47,24 @@ magic_hour/resources/v1/files/upload_urls/client.py,sha256=iKzXLUyKafQoaSv1rwFqd
47
47
  magic_hour/resources/v1/image_background_remover/README.md,sha256=U1mho9NCQLbYgL0WsUxLgqxxSpZqGzl2RONR2cwHRgc,733
48
48
  magic_hour/resources/v1/image_background_remover/__init__.py,sha256=Vb_e8zKEh7bdrq0q1175DqyOd1ptPBUIfSKSLFPBVU4,166
49
49
  magic_hour/resources/v1/image_background_remover/client.py,sha256=0eUSR5tWSKl5Hc7gp5XPzWBG-Vt7-_0pDjkfcePKO4M,3751
50
- magic_hour/resources/v1/image_projects/README.md,sha256=eWG-p7x8FMDFW8HecB0-MMnXkpdGtddiI-tE9lh75vM,1513
50
+ magic_hour/resources/v1/image_projects/README.md,sha256=S7r_5dtF4Ggfh_mIbEEtFLFa1CdOFSKoMr2JIS64hb4,1589
51
51
  magic_hour/resources/v1/image_projects/__init__.py,sha256=oBlV4e5IVYe8SclhoEy2VOYB53kKP2DORXwcztAwU3E,130
52
- magic_hour/resources/v1/image_projects/client.py,sha256=ufQy3w6xNiFRIM_WlXhUqmZg1o5tZmmq9vB4C13vRr4,5463
53
- magic_hour/resources/v1/image_to_video/README.md,sha256=n82x0Hy0HF0QD-AQgpOpgMnJaUYSDMMFR08YBX6TO5Q,1144
52
+ magic_hour/resources/v1/image_projects/client.py,sha256=M6MwgQokexGimn9aZn4bQNKSMBtDia44BNVwIlw9VSE,5539
53
+ magic_hour/resources/v1/image_to_video/README.md,sha256=oT5WB-JGAC4CnwMGrUwKzNCDXDwU350w7b6vtGtFDiE,1148
54
54
  magic_hour/resources/v1/image_to_video/__init__.py,sha256=tY_ABo6evwKQBRSq-M84lNX-pXqmxoozukmrO6NhCgA,126
55
- magic_hour/resources/v1/image_to_video/client.py,sha256=d3IA58ZsyliNGEksOv_0-D-bE4ihBHF5woRLNraTJW4,5951
56
- magic_hour/resources/v1/lip_sync/README.md,sha256=mR84XXa4HPr8NJwsoIAkn0U-gaOQsCXis9XB8EqayOU,1318
55
+ magic_hour/resources/v1/image_to_video/client.py,sha256=1hj-H1LgvMRs669Dcm1h1FSfxDESEo0SIrK3DSi0Qso,5955
56
+ magic_hour/resources/v1/lip_sync/README.md,sha256=7n7vA4A92iGhs3n4EVAX1pQWXE4hO_gWq_DSH4n7nTY,1330
57
57
  magic_hour/resources/v1/lip_sync/__init__.py,sha256=MlKUAoHNSKcuNzVyqNfLnLtD_PsqEn3l1TtVpPC1JqQ,106
58
- magic_hour/resources/v1/lip_sync/client.py,sha256=QOtaZUEVe7cH1-sfIo6Uw1_mz8E-bSMt1YAphWxrp-M,7087
59
- magic_hour/resources/v1/text_to_video/README.md,sha256=zY9yAyubiimygf7SpIGmGGRUQcx9b4rhu_Xnw8ukhaw,1034
58
+ magic_hour/resources/v1/lip_sync/client.py,sha256=IsqZ6xh9wy5olrXuY4TdqbqbTXj-6dA4vDfThMIbfQs,7099
59
+ magic_hour/resources/v1/text_to_video/README.md,sha256=Ug9HDXxOE0L_4sY6M4vmP041m8mPCxpabM4QbQoGMjY,1038
60
60
  magic_hour/resources/v1/text_to_video/__init__.py,sha256=F18iHSi9tuYSdgpatznBzb7lbSySNpK-82w96-Om_k4,122
61
- magic_hour/resources/v1/text_to_video/client.py,sha256=-KS2Kw0iI0PwzHlHDPqgEI-zt5ka_37FdCMFWZ1RuTM,4992
62
- magic_hour/resources/v1/video_projects/README.md,sha256=_pNhL1hqHu6UG5X-4Qyb4BUwlagpyzM56ofUzm1AWH4,1513
61
+ magic_hour/resources/v1/text_to_video/client.py,sha256=60cYTtIN8vTjsxVVxq42EJ8679XQj4jiC2kiPyqHMX0,4996
62
+ magic_hour/resources/v1/video_projects/README.md,sha256=jrNLefS4AacIUPUSjUwOfcEWHCyKl1VnXHmluSg5B90,1589
63
63
  magic_hour/resources/v1/video_projects/__init__.py,sha256=1aj_tE-GAf8BuQ76RQvjGVn8Y39CjdAJDlcsCPucX0w,130
64
- magic_hour/resources/v1/video_projects/client.py,sha256=hf9bSAfY4eDMu-xvRzYrR0pJtB4RA7uOqNTxPxm4jD0,5447
65
- magic_hour/resources/v1/video_to_video/README.md,sha256=xbKMVsTx4hbPELZhrO0SW8vtrIG8uvIF87fUEliFhUs,1604
64
+ magic_hour/resources/v1/video_projects/client.py,sha256=O8lA3LgpFDqYxE3R6BLfQIMse1KNBqy2Q5LacKySod4,5523
65
+ magic_hour/resources/v1/video_to_video/README.md,sha256=WqR3PqpIHDQm0P-67pG6nD0M4mHM8qHXjrXjMCqUHOM,1612
66
66
  magic_hour/resources/v1/video_to_video/__init__.py,sha256=1SHaRLlsrlBkdxxKBYgdbHrGATlRvqlXc22RpjjHaOA,126
67
- magic_hour/resources/v1/video_to_video/client.py,sha256=l6aDk9Het3m95ElcZiPkEBzXppN6YmZHhw4vMC8sV50,8154
67
+ magic_hour/resources/v1/video_to_video/client.py,sha256=2ITdQuy2t8oxQCJ23YKqX_XSmQpoiJlivP6wo5qwDn8,8162
68
68
  magic_hour/types/models/__init__.py,sha256=fNhJtvVAFiSKzXt57UnvNozv6QYWZURU36pWTVoZQ-M,2790
69
69
  magic_hour/types/models/get_v1_image_projects_id_response.py,sha256=8SsbpC_HTl1TDmqRgpKK7Vbj4kM_vnuhm_AZ4uIaf80,2140
70
70
  magic_hour/types/models/get_v1_image_projects_id_response_downloads_item.py,sha256=ianjZWSKWOIRWAtakZKO_nn9YZXUuhVRatzXJnzQwKg,412
@@ -125,7 +125,7 @@ magic_hour/types/params/post_v1_text_to_video_body_style.py,sha256=8-zyz32Gz3qke
125
125
  magic_hour/types/params/post_v1_video_to_video_body.py,sha256=dbazkWaGco73sSirtD-Q6vnLzF4BZa4Od87UTih1_-o,2914
126
126
  magic_hour/types/params/post_v1_video_to_video_body_assets.py,sha256=INe1iMyS-xhj00xO7OymdqFrmgp1fWykDdLo_uoewVs,1456
127
127
  magic_hour/types/params/post_v1_video_to_video_body_style.py,sha256=RQWr8VJqipM4YmNwGCQXWf7XT7O8Y2hgqxRRH1Vh7Cs,5457
128
- magic_hour-0.8.1.dist-info/LICENSE,sha256=F3fxj7JXPgB2K0uj8YXRsVss4u-Dgt_-U3V4VXsivNI,1070
129
- magic_hour-0.8.1.dist-info/METADATA,sha256=oa0gO6dLHnrVJBEvPZPgJXWlt9iKrVCoLtGr_thKa-w,4683
130
- magic_hour-0.8.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
131
- magic_hour-0.8.1.dist-info/RECORD,,
128
+ magic_hour-0.8.3.dist-info/LICENSE,sha256=F3fxj7JXPgB2K0uj8YXRsVss4u-Dgt_-U3V4VXsivNI,1070
129
+ magic_hour-0.8.3.dist-info/METADATA,sha256=f68spk5_Pn3Xs7dGBxAw4anPL3JErpic8YcAnLY25OE,4654
130
+ magic_hour-0.8.3.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
131
+ magic_hour-0.8.3.dist-info/RECORD,,