magic_hour 0.8.1__tar.gz → 0.8.3__tar.gz

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.

Files changed (132) hide show
  1. {magic_hour-0.8.1 → magic_hour-0.8.3}/PKG-INFO +18 -23
  2. {magic_hour-0.8.1 → magic_hour-0.8.3}/README.md +16 -21
  3. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/core/api_error.py +15 -7
  4. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/core/base_client.py +48 -30
  5. magic_hour-0.8.3/magic_hour/core/utils.py +55 -0
  6. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/environment.py +1 -1
  7. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/animation/README.md +4 -4
  8. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/animation/client.py +4 -4
  9. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/face_swap/README.md +4 -4
  10. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/face_swap/client.py +4 -4
  11. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/image_projects/README.md +4 -4
  12. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/image_projects/client.py +4 -4
  13. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/image_to_video/README.md +2 -2
  14. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/image_to_video/client.py +2 -2
  15. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/lip_sync/README.md +6 -6
  16. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/lip_sync/client.py +6 -6
  17. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/text_to_video/README.md +2 -2
  18. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/text_to_video/client.py +2 -2
  19. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/video_projects/README.md +4 -4
  20. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/video_projects/client.py +4 -4
  21. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/video_to_video/README.md +4 -4
  22. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/video_to_video/client.py +4 -4
  23. {magic_hour-0.8.1 → magic_hour-0.8.3}/pyproject.toml +2 -2
  24. magic_hour-0.8.1/magic_hour/core/utils.py +0 -38
  25. {magic_hour-0.8.1 → magic_hour-0.8.3}/LICENSE +0 -0
  26. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/__init__.py +0 -0
  27. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/client.py +4 -4
  28. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/core/__init__.py +0 -0
  29. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/core/auth.py +0 -0
  30. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/core/binary_response.py +0 -0
  31. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/core/request.py +0 -0
  32. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/core/response.py +0 -0
  33. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/core/type_utils.py +0 -0
  34. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/__init__.py +0 -0
  35. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/ai_clothes_changer/README.md +0 -0
  36. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/ai_clothes_changer/__init__.py +0 -0
  37. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/ai_clothes_changer/client.py +0 -0
  38. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/ai_headshot_generator/README.md +0 -0
  39. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/ai_headshot_generator/__init__.py +0 -0
  40. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/ai_headshot_generator/client.py +0 -0
  41. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/ai_image_generator/README.md +0 -0
  42. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/ai_image_generator/__init__.py +0 -0
  43. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/ai_image_generator/client.py +0 -0
  44. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/ai_image_upscaler/README.md +0 -0
  45. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/ai_image_upscaler/__init__.py +0 -0
  46. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/ai_image_upscaler/client.py +0 -0
  47. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/ai_photo_editor/README.md +0 -0
  48. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/ai_photo_editor/__init__.py +0 -0
  49. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/ai_photo_editor/client.py +0 -0
  50. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/ai_qr_code_generator/README.md +0 -0
  51. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/ai_qr_code_generator/__init__.py +0 -0
  52. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/ai_qr_code_generator/client.py +0 -0
  53. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/animation/__init__.py +0 -0
  54. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/client.py +0 -0
  55. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/face_swap/__init__.py +0 -0
  56. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/face_swap_photo/README.md +0 -0
  57. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/face_swap_photo/__init__.py +0 -0
  58. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/face_swap_photo/client.py +0 -0
  59. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/files/__init__.py +0 -0
  60. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/files/client.py +0 -0
  61. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/files/upload_urls/README.md +0 -0
  62. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/files/upload_urls/__init__.py +0 -0
  63. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/files/upload_urls/client.py +0 -0
  64. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/image_background_remover/README.md +0 -0
  65. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/image_background_remover/__init__.py +0 -0
  66. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/image_background_remover/client.py +0 -0
  67. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/image_projects/__init__.py +0 -0
  68. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/image_to_video/__init__.py +0 -0
  69. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/lip_sync/__init__.py +0 -0
  70. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/text_to_video/__init__.py +0 -0
  71. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/video_projects/__init__.py +0 -0
  72. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/resources/v1/video_to_video/__init__.py +0 -0
  73. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/__init__.py +0 -0
  74. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/get_v1_image_projects_id_response.py +0 -0
  75. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/get_v1_image_projects_id_response_downloads_item.py +0 -0
  76. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/get_v1_image_projects_id_response_error.py +0 -0
  77. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/get_v1_video_projects_id_response.py +0 -0
  78. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/get_v1_video_projects_id_response_download.py +0 -0
  79. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/get_v1_video_projects_id_response_downloads_item.py +0 -0
  80. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/get_v1_video_projects_id_response_error.py +0 -0
  81. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/post_v1_ai_clothes_changer_response.py +0 -0
  82. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/post_v1_ai_headshot_generator_response.py +0 -0
  83. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/post_v1_ai_image_generator_response.py +0 -0
  84. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/post_v1_ai_image_upscaler_response.py +0 -0
  85. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/post_v1_ai_photo_editor_response.py +0 -0
  86. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/post_v1_ai_qr_code_generator_response.py +0 -0
  87. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/post_v1_animation_response.py +0 -0
  88. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/post_v1_face_swap_photo_response.py +0 -0
  89. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/post_v1_face_swap_response.py +0 -0
  90. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/post_v1_files_upload_urls_response.py +0 -0
  91. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/post_v1_files_upload_urls_response_items_item.py +0 -0
  92. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/post_v1_image_background_remover_response.py +0 -0
  93. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/post_v1_image_to_video_response.py +0 -0
  94. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/post_v1_lip_sync_response.py +0 -0
  95. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/post_v1_text_to_video_response.py +0 -0
  96. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/models/post_v1_video_to_video_response.py +0 -0
  97. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/__init__.py +0 -0
  98. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_ai_clothes_changer_body.py +0 -0
  99. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_ai_clothes_changer_body_assets.py +0 -0
  100. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_ai_headshot_generator_body.py +0 -0
  101. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_ai_headshot_generator_body_assets.py +0 -0
  102. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_ai_image_generator_body.py +0 -0
  103. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_ai_image_generator_body_style.py +0 -0
  104. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_ai_image_upscaler_body.py +0 -0
  105. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_ai_image_upscaler_body_assets.py +0 -0
  106. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_ai_image_upscaler_body_style.py +0 -0
  107. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_ai_photo_editor_body.py +0 -0
  108. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_ai_photo_editor_body_assets.py +0 -0
  109. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_ai_photo_editor_body_style.py +0 -0
  110. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_ai_qr_code_generator_body.py +0 -0
  111. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_ai_qr_code_generator_body_style.py +0 -0
  112. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_animation_body.py +0 -0
  113. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_animation_body_assets.py +0 -0
  114. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_animation_body_style.py +0 -0
  115. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_face_swap_body.py +0 -0
  116. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_face_swap_body_assets.py +0 -0
  117. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_face_swap_photo_body.py +0 -0
  118. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_face_swap_photo_body_assets.py +0 -0
  119. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_files_upload_urls_body.py +0 -0
  120. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_files_upload_urls_body_items_item.py +0 -0
  121. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_image_background_remover_body.py +0 -0
  122. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_image_background_remover_body_assets.py +0 -0
  123. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_image_to_video_body.py +0 -0
  124. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_image_to_video_body_assets.py +0 -0
  125. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_image_to_video_body_style.py +0 -0
  126. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_lip_sync_body.py +0 -0
  127. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_lip_sync_body_assets.py +0 -0
  128. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_text_to_video_body.py +0 -0
  129. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_text_to_video_body_style.py +0 -0
  130. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_video_to_video_body.py +0 -0
  131. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_video_to_video_body_assets.py +0 -0
  132. {magic_hour-0.8.1 → magic_hour-0.8.3}/magic_hour/types/params/post_v1_video_to_video_body_style.py +0 -0
@@ -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,49 +1,44 @@
1
+ # Magic Hour Python SDK
1
2
 
2
- # Magic Hour API Python SDK
3
+ [![PyPI - Version](https://img.shields.io/pypi/v/magic_hour)](https://pypi.org/project/magic_hour/)
3
4
 
4
- ## Overview
5
-
6
- # Introduction
7
-
8
- Magic Hour provides an API (beta) that can be integrated into your own application to generate videos using AI.
5
+ Magic Hour provides an API (beta) that can be integrated into your own application to generate videos and images using AI.
9
6
 
10
7
  Webhook documentation can be found [here](https://magichour.ai/docs/webhook).
11
8
 
12
9
  If you have any questions, please reach out to us via [discord](https://discord.gg/JX5rgsZaJp).
13
10
 
14
- # Authentication
15
-
16
- Every request requires an API key.
17
-
18
- To get started, first generate your API key [here](https://magichour.ai/settings/developer).
11
+ ## Install
19
12
 
20
- Then, add the `Authorization` header to the request.
21
-
22
- | Key | Value |
23
- |-|-|
24
- | Authorization | Bearer mhk_live_apikey |
13
+ ```
14
+ pip install magic_hour
15
+ ```
25
16
 
26
- > **Warning**: any API call that renders a video will utilize frames in your account.
17
+ ## Usage
27
18
 
19
+ Initialize the client
28
20
 
29
21
  ### Synchronous Client
30
22
 
31
23
  ```python
32
24
  from magic_hour import Client
33
- from os import getenv
34
25
 
35
- client = Client(token=getenv("API_TOKEN"))
26
+ # generate your API Key at https://magichour.ai/developer
27
+ client = Client(token="my api key")
36
28
  ```
37
29
 
38
30
  ### Asynchronous Client
39
31
 
40
32
  ```python
41
33
  from magic_hour import AsyncClient
42
- from os import getenv
43
34
 
44
- client = AsyncClient(token=getenv("API_TOKEN"))
35
+ # generate your API Key at https://magichour.ai/developer
36
+ client = AsyncClient(token="my api key")
45
37
  ```
46
38
 
39
+ > [!WARNING]
40
+ > Any API call that renders a video will utilize frames in your account.
41
+
47
42
  ## Module Documentation and Snippets
48
43
 
49
44
  ### [v1.ai_clothes_changer](magic_hour/resources/v1/ai_clothes_changer/README.md)
@@ -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(
@@ -0,0 +1,55 @@
1
+ import typing
2
+ import re
3
+ from typing_extensions import Literal
4
+
5
+ import httpx
6
+
7
+ from .binary_response import BinaryResponse
8
+
9
+
10
+ def remove_none_from_dict(
11
+ original: typing.Dict[str, typing.Optional[typing.Any]],
12
+ ) -> typing.Dict[str, typing.Any]:
13
+ new: typing.Dict[str, typing.Any] = {}
14
+ for key, value in original.items():
15
+ if value is not None:
16
+ new[key] = value
17
+ return new
18
+
19
+
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
@@ -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",