magic_hour 0.8.2__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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: magic_hour
3
- Version: 0.8.2
3
+ Version: 0.8.3
4
4
  Summary: Python SDK for Magic Hour API
5
5
  Requires-Python: >=3.8,<4.0
6
6
  Classifier: Programming Language :: Python :: 3
@@ -18,7 +18,7 @@ Description-Content-Type: text/markdown
18
18
 
19
19
  # Magic Hour Python SDK
20
20
 
21
- ![PyPI - Version](https://img.shields.io/pypi/v/magic_hour)
21
+ [![PyPI - Version](https://img.shields.io/pypi/v/magic_hour)](https://pypi.org/project/magic_hour/)
22
22
 
23
23
  Magic Hour provides an API (beta) that can be integrated into your own application to generate videos and images using AI.
24
24
 
@@ -54,79 +54,80 @@ from magic_hour import AsyncClient
54
54
  client = AsyncClient(token="my api key")
55
55
  ```
56
56
 
57
- > **Warning**: any API call that renders a video will utilize frames in your account.
57
+ > [!WARNING]
58
+ > Any API call that renders a video will utilize frames in your account.
58
59
 
59
60
  ## Module Documentation and Snippets
60
61
 
61
62
  ### [v1.ai_clothes_changer](magic_hour/resources/v1/ai_clothes_changer/README.md)
62
63
 
63
- - [create](magic_hour/resources/v1/ai_clothes_changer/README.md#create) - AI Clothes Changer
64
+ * [create](magic_hour/resources/v1/ai_clothes_changer/README.md#create) - AI Clothes Changer
64
65
 
65
66
  ### [v1.ai_headshot_generator](magic_hour/resources/v1/ai_headshot_generator/README.md)
66
67
 
67
- - [create](magic_hour/resources/v1/ai_headshot_generator/README.md#create) - AI Headshots
68
+ * [create](magic_hour/resources/v1/ai_headshot_generator/README.md#create) - AI Headshots
68
69
 
69
70
  ### [v1.ai_image_generator](magic_hour/resources/v1/ai_image_generator/README.md)
70
71
 
71
- - [create](magic_hour/resources/v1/ai_image_generator/README.md#create) - AI Images
72
+ * [create](magic_hour/resources/v1/ai_image_generator/README.md#create) - AI Images
72
73
 
73
74
  ### [v1.ai_image_upscaler](magic_hour/resources/v1/ai_image_upscaler/README.md)
74
75
 
75
- - [create](magic_hour/resources/v1/ai_image_upscaler/README.md#create) - AI Image Upscaler
76
+ * [create](magic_hour/resources/v1/ai_image_upscaler/README.md#create) - AI Image Upscaler
76
77
 
77
78
  ### [v1.ai_photo_editor](magic_hour/resources/v1/ai_photo_editor/README.md)
78
79
 
79
- - [create](magic_hour/resources/v1/ai_photo_editor/README.md#create) - AI Photo Editor
80
+ * [create](magic_hour/resources/v1/ai_photo_editor/README.md#create) - AI Photo Editor
80
81
 
81
82
  ### [v1.ai_qr_code_generator](magic_hour/resources/v1/ai_qr_code_generator/README.md)
82
83
 
83
- - [create](magic_hour/resources/v1/ai_qr_code_generator/README.md#create) - AI QR Code
84
+ * [create](magic_hour/resources/v1/ai_qr_code_generator/README.md#create) - AI QR Code
84
85
 
85
86
  ### [v1.animation](magic_hour/resources/v1/animation/README.md)
86
87
 
87
- - [create](magic_hour/resources/v1/animation/README.md#create) - Animation
88
+ * [create](magic_hour/resources/v1/animation/README.md#create) - Animation
88
89
 
89
90
  ### [v1.face_swap](magic_hour/resources/v1/face_swap/README.md)
90
91
 
91
- - [create](magic_hour/resources/v1/face_swap/README.md#create) - Face Swap video
92
+ * [create](magic_hour/resources/v1/face_swap/README.md#create) - Face Swap video
92
93
 
93
94
  ### [v1.face_swap_photo](magic_hour/resources/v1/face_swap_photo/README.md)
94
95
 
95
- - [create](magic_hour/resources/v1/face_swap_photo/README.md#create) - Face Swap Photo
96
+ * [create](magic_hour/resources/v1/face_swap_photo/README.md#create) - Face Swap Photo
96
97
 
97
98
  ### [v1.files.upload_urls](magic_hour/resources/v1/files/upload_urls/README.md)
98
99
 
99
- - [create](magic_hour/resources/v1/files/upload_urls/README.md#create) - Generate asset upload urls
100
+ * [create](magic_hour/resources/v1/files/upload_urls/README.md#create) - Generate asset upload urls
100
101
 
101
102
  ### [v1.image_background_remover](magic_hour/resources/v1/image_background_remover/README.md)
102
103
 
103
- - [create](magic_hour/resources/v1/image_background_remover/README.md#create) - Image Background Remover
104
+ * [create](magic_hour/resources/v1/image_background_remover/README.md#create) - Image Background Remover
104
105
 
105
106
  ### [v1.image_projects](magic_hour/resources/v1/image_projects/README.md)
106
107
 
107
- - [delete](magic_hour/resources/v1/image_projects/README.md#delete) - Delete image
108
- - [get](magic_hour/resources/v1/image_projects/README.md#get) - Get image details
108
+ * [delete](magic_hour/resources/v1/image_projects/README.md#delete) - Delete image
109
+ * [get](magic_hour/resources/v1/image_projects/README.md#get) - Get image details
109
110
 
110
111
  ### [v1.image_to_video](magic_hour/resources/v1/image_to_video/README.md)
111
112
 
112
- - [create](magic_hour/resources/v1/image_to_video/README.md#create) - Image-to-Video
113
+ * [create](magic_hour/resources/v1/image_to_video/README.md#create) - Image-to-Video
113
114
 
114
115
  ### [v1.lip_sync](magic_hour/resources/v1/lip_sync/README.md)
115
116
 
116
- - [create](magic_hour/resources/v1/lip_sync/README.md#create) - Lip Sync
117
+ * [create](magic_hour/resources/v1/lip_sync/README.md#create) - Lip Sync
117
118
 
118
119
  ### [v1.text_to_video](magic_hour/resources/v1/text_to_video/README.md)
119
120
 
120
- - [create](magic_hour/resources/v1/text_to_video/README.md#create) - Text-to-Video
121
+ * [create](magic_hour/resources/v1/text_to_video/README.md#create) - Text-to-Video
121
122
 
122
123
  ### [v1.video_projects](magic_hour/resources/v1/video_projects/README.md)
123
124
 
124
- - [delete](magic_hour/resources/v1/video_projects/README.md#delete) - Delete video
125
- - [get](magic_hour/resources/v1/video_projects/README.md#get) - Get video details
125
+ * [delete](magic_hour/resources/v1/video_projects/README.md#delete) - Delete video
126
+ * [get](magic_hour/resources/v1/video_projects/README.md#get) - Get video details
126
127
 
127
128
  ### [v1.video_to_video](magic_hour/resources/v1/video_to_video/README.md)
128
129
 
129
- - [create](magic_hour/resources/v1/video_to_video/README.md#create) - Video-to-Video
130
+ * [create](magic_hour/resources/v1/video_to_video/README.md#create) - Video-to-Video
130
131
 
131
132
  <!-- MODULE DOCS END -->
132
133
 
@@ -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.2.dist-info/LICENSE,sha256=F3fxj7JXPgB2K0uj8YXRsVss4u-Dgt_-U3V4VXsivNI,1070
129
- magic_hour-0.8.2.dist-info/METADATA,sha256=xNV74ROJv9owXnY6vZ0mArNRtB1PJ8G8QnCGSE7IwUM,4614
130
- magic_hour-0.8.2.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
131
- magic_hour-0.8.2.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,,