magic_hour 0.9.5__py3-none-any.whl → 0.44.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. magic_hour/README.md +34 -0
  2. magic_hour/__init__.py +1 -1
  3. magic_hour/client.py +8 -17
  4. magic_hour/environment.py +13 -1
  5. magic_hour/helpers/__init__.py +4 -0
  6. magic_hour/helpers/download.py +77 -0
  7. magic_hour/helpers/logger.py +8 -0
  8. magic_hour/resources/v1/README.md +32 -0
  9. magic_hour/resources/v1/ai_clothes_changer/README.md +94 -5
  10. magic_hour/resources/v1/ai_clothes_changer/client.py +161 -16
  11. magic_hour/resources/v1/ai_face_editor/README.md +195 -0
  12. magic_hour/resources/v1/ai_face_editor/__init__.py +4 -0
  13. magic_hour/resources/v1/ai_face_editor/client.py +324 -0
  14. magic_hour/resources/v1/ai_gif_generator/README.md +116 -0
  15. magic_hour/resources/v1/ai_gif_generator/__init__.py +4 -0
  16. magic_hour/resources/v1/ai_gif_generator/client.py +257 -0
  17. magic_hour/resources/v1/ai_headshot_generator/README.md +81 -3
  18. magic_hour/resources/v1/ai_headshot_generator/client.py +167 -18
  19. magic_hour/resources/v1/ai_image_editor/README.md +125 -0
  20. magic_hour/resources/v1/ai_image_editor/__init__.py +4 -0
  21. magic_hour/resources/v1/ai_image_editor/client.py +290 -0
  22. magic_hour/resources/v1/ai_image_generator/README.md +99 -5
  23. magic_hour/resources/v1/ai_image_generator/client.py +170 -24
  24. magic_hour/resources/v1/ai_image_upscaler/README.md +89 -3
  25. magic_hour/resources/v1/ai_image_upscaler/client.py +173 -20
  26. magic_hour/resources/v1/ai_meme_generator/README.md +129 -0
  27. magic_hour/resources/v1/ai_meme_generator/__init__.py +4 -0
  28. magic_hour/resources/v1/ai_meme_generator/client.py +253 -0
  29. magic_hour/resources/v1/ai_photo_editor/README.md +119 -4
  30. magic_hour/resources/v1/ai_photo_editor/client.py +199 -18
  31. magic_hour/resources/v1/ai_qr_code_generator/README.md +84 -3
  32. magic_hour/resources/v1/ai_qr_code_generator/client.py +140 -18
  33. magic_hour/resources/v1/ai_talking_photo/README.md +137 -0
  34. magic_hour/resources/v1/ai_talking_photo/__init__.py +4 -0
  35. magic_hour/resources/v1/ai_talking_photo/client.py +326 -0
  36. magic_hour/resources/v1/ai_voice_cloner/README.md +62 -0
  37. magic_hour/resources/v1/ai_voice_cloner/__init__.py +4 -0
  38. magic_hour/resources/v1/ai_voice_cloner/client.py +272 -0
  39. magic_hour/resources/v1/ai_voice_generator/README.md +112 -0
  40. magic_hour/resources/v1/ai_voice_generator/__init__.py +4 -0
  41. magic_hour/resources/v1/ai_voice_generator/client.py +241 -0
  42. magic_hour/resources/v1/animation/README.md +128 -6
  43. magic_hour/resources/v1/animation/client.py +247 -22
  44. magic_hour/resources/v1/audio_projects/README.md +135 -0
  45. magic_hour/resources/v1/audio_projects/__init__.py +12 -0
  46. magic_hour/resources/v1/audio_projects/client.py +310 -0
  47. magic_hour/resources/v1/audio_projects/client_test.py +520 -0
  48. magic_hour/resources/v1/auto_subtitle_generator/README.md +128 -0
  49. magic_hour/resources/v1/auto_subtitle_generator/__init__.py +4 -0
  50. magic_hour/resources/v1/auto_subtitle_generator/client.py +346 -0
  51. magic_hour/resources/v1/client.py +75 -1
  52. magic_hour/resources/v1/face_detection/README.md +157 -0
  53. magic_hour/resources/v1/face_detection/__init__.py +12 -0
  54. magic_hour/resources/v1/face_detection/client.py +380 -0
  55. magic_hour/resources/v1/face_swap/README.md +137 -9
  56. magic_hour/resources/v1/face_swap/client.py +329 -38
  57. magic_hour/resources/v1/face_swap_photo/README.md +118 -3
  58. magic_hour/resources/v1/face_swap_photo/client.py +199 -14
  59. magic_hour/resources/v1/files/README.md +39 -0
  60. magic_hour/resources/v1/files/client.py +351 -1
  61. magic_hour/resources/v1/files/client_test.py +414 -0
  62. magic_hour/resources/v1/files/upload_urls/README.md +38 -17
  63. magic_hour/resources/v1/files/upload_urls/client.py +38 -34
  64. magic_hour/resources/v1/image_background_remover/README.md +96 -5
  65. magic_hour/resources/v1/image_background_remover/client.py +151 -16
  66. magic_hour/resources/v1/image_projects/README.md +82 -10
  67. magic_hour/resources/v1/image_projects/__init__.py +10 -2
  68. magic_hour/resources/v1/image_projects/client.py +154 -16
  69. magic_hour/resources/v1/image_projects/client_test.py +527 -0
  70. magic_hour/resources/v1/image_to_video/README.md +96 -11
  71. magic_hour/resources/v1/image_to_video/client.py +282 -38
  72. magic_hour/resources/v1/lip_sync/README.md +112 -9
  73. magic_hour/resources/v1/lip_sync/client.py +288 -34
  74. magic_hour/resources/v1/photo_colorizer/README.md +107 -0
  75. magic_hour/resources/v1/photo_colorizer/__init__.py +4 -0
  76. magic_hour/resources/v1/photo_colorizer/client.py +248 -0
  77. magic_hour/resources/v1/text_to_video/README.md +96 -7
  78. magic_hour/resources/v1/text_to_video/client.py +204 -18
  79. magic_hour/resources/v1/video_projects/README.md +81 -9
  80. magic_hour/resources/v1/video_projects/__init__.py +10 -2
  81. magic_hour/resources/v1/video_projects/client.py +151 -14
  82. magic_hour/resources/v1/video_projects/client_test.py +527 -0
  83. magic_hour/resources/v1/video_to_video/README.md +119 -15
  84. magic_hour/resources/v1/video_to_video/client.py +299 -46
  85. magic_hour/types/models/__init__.py +92 -56
  86. magic_hour/types/models/v1_ai_clothes_changer_create_response.py +33 -0
  87. magic_hour/types/models/v1_ai_face_editor_create_response.py +33 -0
  88. magic_hour/types/models/v1_ai_gif_generator_create_response.py +33 -0
  89. magic_hour/types/models/v1_ai_headshot_generator_create_response.py +33 -0
  90. magic_hour/types/models/v1_ai_image_editor_create_response.py +33 -0
  91. magic_hour/types/models/v1_ai_image_generator_create_response.py +33 -0
  92. magic_hour/types/models/v1_ai_image_upscaler_create_response.py +33 -0
  93. magic_hour/types/models/v1_ai_meme_generator_create_response.py +33 -0
  94. magic_hour/types/models/v1_ai_photo_editor_create_response.py +33 -0
  95. magic_hour/types/models/v1_ai_qr_code_generator_create_response.py +33 -0
  96. magic_hour/types/models/v1_ai_talking_photo_create_response.py +35 -0
  97. magic_hour/types/models/v1_ai_voice_cloner_create_response.py +27 -0
  98. magic_hour/types/models/v1_ai_voice_generator_create_response.py +27 -0
  99. magic_hour/types/models/v1_animation_create_response.py +35 -0
  100. magic_hour/types/models/v1_audio_projects_get_response.py +72 -0
  101. magic_hour/types/models/v1_audio_projects_get_response_downloads_item.py +19 -0
  102. magic_hour/types/models/{get_v1_image_projects_id_response_error.py → v1_audio_projects_get_response_error.py} +2 -2
  103. magic_hour/types/models/v1_auto_subtitle_generator_create_response.py +35 -0
  104. magic_hour/types/models/v1_face_detection_create_response.py +25 -0
  105. magic_hour/types/models/v1_face_detection_get_response.py +45 -0
  106. magic_hour/types/models/v1_face_detection_get_response_faces_item.py +25 -0
  107. magic_hour/types/models/v1_face_swap_create_response.py +35 -0
  108. magic_hour/types/models/v1_face_swap_photo_create_response.py +33 -0
  109. magic_hour/types/models/v1_files_upload_urls_create_response.py +24 -0
  110. magic_hour/types/models/{post_v1_files_upload_urls_response_items_item.py → v1_files_upload_urls_create_response_items_item.py} +2 -2
  111. magic_hour/types/models/v1_image_background_remover_create_response.py +33 -0
  112. magic_hour/types/models/{get_v1_image_projects_id_response.py → v1_image_projects_get_response.py} +20 -18
  113. magic_hour/types/models/{get_v1_video_projects_id_response_downloads_item.py → v1_image_projects_get_response_downloads_item.py} +1 -1
  114. magic_hour/types/models/{get_v1_video_projects_id_response_error.py → v1_image_projects_get_response_error.py} +2 -2
  115. magic_hour/types/models/v1_image_to_video_create_response.py +35 -0
  116. magic_hour/types/models/v1_lip_sync_create_response.py +35 -0
  117. magic_hour/types/models/v1_photo_colorizer_create_response.py +33 -0
  118. magic_hour/types/models/v1_text_to_video_create_response.py +35 -0
  119. magic_hour/types/models/{get_v1_video_projects_id_response.py → v1_video_projects_get_response.py} +26 -23
  120. magic_hour/types/models/{get_v1_video_projects_id_response_download.py → v1_video_projects_get_response_download.py} +1 -1
  121. magic_hour/types/models/{get_v1_image_projects_id_response_downloads_item.py → v1_video_projects_get_response_downloads_item.py} +1 -1
  122. magic_hour/types/models/v1_video_projects_get_response_error.py +25 -0
  123. magic_hour/types/models/v1_video_to_video_create_response.py +35 -0
  124. magic_hour/types/params/__init__.py +422 -176
  125. magic_hour/types/params/v1_ai_clothes_changer_create_body.py +40 -0
  126. magic_hour/types/params/v1_ai_clothes_changer_create_body_assets.py +58 -0
  127. magic_hour/types/params/v1_ai_clothes_changer_generate_body_assets.py +33 -0
  128. magic_hour/types/params/v1_ai_face_editor_create_body.py +52 -0
  129. magic_hour/types/params/v1_ai_face_editor_create_body_assets.py +33 -0
  130. magic_hour/types/params/v1_ai_face_editor_create_body_style.py +137 -0
  131. magic_hour/types/params/v1_ai_face_editor_generate_body_assets.py +17 -0
  132. magic_hour/types/params/v1_ai_gif_generator_create_body.py +47 -0
  133. magic_hour/types/params/{post_v1_ai_image_generator_body_style.py → v1_ai_gif_generator_create_body_style.py} +5 -5
  134. magic_hour/types/params/v1_ai_headshot_generator_create_body.py +49 -0
  135. magic_hour/types/params/v1_ai_headshot_generator_create_body_assets.py +33 -0
  136. magic_hour/types/params/v1_ai_headshot_generator_create_body_style.py +27 -0
  137. magic_hour/types/params/v1_ai_headshot_generator_generate_body_assets.py +17 -0
  138. magic_hour/types/params/v1_ai_image_editor_create_body.py +49 -0
  139. magic_hour/types/params/v1_ai_image_editor_create_body_assets.py +47 -0
  140. magic_hour/types/params/v1_ai_image_editor_create_body_style.py +41 -0
  141. magic_hour/types/params/v1_ai_image_editor_generate_body_assets.py +28 -0
  142. magic_hour/types/params/{post_v1_ai_image_generator_body.py → v1_ai_image_generator_create_body.py} +17 -11
  143. magic_hour/types/params/v1_ai_image_generator_create_body_style.py +127 -0
  144. magic_hour/types/params/v1_ai_image_upscaler_create_body.py +59 -0
  145. magic_hour/types/params/v1_ai_image_upscaler_create_body_assets.py +33 -0
  146. magic_hour/types/params/{post_v1_ai_image_upscaler_body_style.py → v1_ai_image_upscaler_create_body_style.py} +4 -4
  147. magic_hour/types/params/v1_ai_image_upscaler_generate_body_assets.py +17 -0
  148. magic_hour/types/params/v1_ai_meme_generator_create_body.py +37 -0
  149. magic_hour/types/params/v1_ai_meme_generator_create_body_style.py +73 -0
  150. magic_hour/types/params/{post_v1_ai_photo_editor_body.py → v1_ai_photo_editor_create_body.py} +15 -15
  151. magic_hour/types/params/v1_ai_photo_editor_create_body_assets.py +33 -0
  152. magic_hour/types/params/{post_v1_ai_photo_editor_body_style.py → v1_ai_photo_editor_create_body_style.py} +20 -4
  153. magic_hour/types/params/v1_ai_photo_editor_generate_body_assets.py +17 -0
  154. magic_hour/types/params/v1_ai_qr_code_generator_create_body.py +45 -0
  155. magic_hour/types/params/{post_v1_ai_qr_code_generator_body_style.py → v1_ai_qr_code_generator_create_body_style.py} +4 -4
  156. magic_hour/types/params/v1_ai_talking_photo_create_body.py +68 -0
  157. magic_hour/types/params/v1_ai_talking_photo_create_body_assets.py +46 -0
  158. magic_hour/types/params/v1_ai_talking_photo_create_body_style.py +44 -0
  159. magic_hour/types/params/v1_ai_talking_photo_generate_body_assets.py +26 -0
  160. magic_hour/types/params/v1_ai_voice_cloner_create_body.py +49 -0
  161. magic_hour/types/params/v1_ai_voice_cloner_create_body_assets.py +33 -0
  162. magic_hour/types/params/v1_ai_voice_cloner_create_body_style.py +28 -0
  163. magic_hour/types/params/v1_ai_voice_cloner_generate_body_assets.py +28 -0
  164. magic_hour/types/params/v1_ai_voice_generator_create_body.py +40 -0
  165. magic_hour/types/params/v1_ai_voice_generator_create_body_style.py +440 -0
  166. magic_hour/types/params/{post_v1_animation_body.py → v1_animation_create_body.py} +16 -16
  167. magic_hour/types/params/{post_v1_animation_body_assets.py → v1_animation_create_body_assets.py} +15 -5
  168. magic_hour/types/params/{post_v1_animation_body_style.py → v1_animation_create_body_style.py} +13 -10
  169. magic_hour/types/params/v1_animation_generate_body_assets.py +39 -0
  170. magic_hour/types/params/v1_auto_subtitle_generator_create_body.py +78 -0
  171. magic_hour/types/params/v1_auto_subtitle_generator_create_body_assets.py +33 -0
  172. magic_hour/types/params/v1_auto_subtitle_generator_create_body_style.py +56 -0
  173. magic_hour/types/params/v1_auto_subtitle_generator_create_body_style_custom_config.py +86 -0
  174. magic_hour/types/params/v1_auto_subtitle_generator_generate_body_assets.py +17 -0
  175. magic_hour/types/params/v1_face_detection_create_body.py +44 -0
  176. magic_hour/types/params/v1_face_detection_create_body_assets.py +33 -0
  177. magic_hour/types/params/v1_face_detection_generate_body_assets.py +17 -0
  178. magic_hour/types/params/v1_face_swap_create_body.py +92 -0
  179. magic_hour/types/params/v1_face_swap_create_body_assets.py +91 -0
  180. magic_hour/types/params/v1_face_swap_create_body_assets_face_mappings_item.py +44 -0
  181. magic_hour/types/params/v1_face_swap_create_body_style.py +33 -0
  182. magic_hour/types/params/v1_face_swap_generate_body_assets.py +56 -0
  183. magic_hour/types/params/v1_face_swap_generate_body_assets_face_mappings_item.py +25 -0
  184. magic_hour/types/params/v1_face_swap_photo_create_body.py +40 -0
  185. magic_hour/types/params/v1_face_swap_photo_create_body_assets.py +76 -0
  186. magic_hour/types/params/v1_face_swap_photo_create_body_assets_face_mappings_item.py +44 -0
  187. magic_hour/types/params/v1_face_swap_photo_generate_body_assets.py +47 -0
  188. magic_hour/types/params/v1_face_swap_photo_generate_body_assets_face_mappings_item.py +25 -0
  189. magic_hour/types/params/v1_files_upload_urls_create_body.py +36 -0
  190. magic_hour/types/params/v1_files_upload_urls_create_body_items_item.py +38 -0
  191. magic_hour/types/params/v1_image_background_remover_create_body.py +40 -0
  192. magic_hour/types/params/v1_image_background_remover_create_body_assets.py +49 -0
  193. magic_hour/types/params/v1_image_background_remover_generate_body_assets.py +27 -0
  194. magic_hour/types/params/v1_image_to_video_create_body.py +101 -0
  195. magic_hour/types/params/v1_image_to_video_create_body_assets.py +33 -0
  196. magic_hour/types/params/v1_image_to_video_create_body_style.py +53 -0
  197. magic_hour/types/params/v1_image_to_video_generate_body_assets.py +17 -0
  198. magic_hour/types/params/v1_lip_sync_create_body.py +100 -0
  199. magic_hour/types/params/{post_v1_lip_sync_body_assets.py → v1_lip_sync_create_body_assets.py} +15 -5
  200. magic_hour/types/params/v1_lip_sync_create_body_style.py +37 -0
  201. magic_hour/types/params/v1_lip_sync_generate_body_assets.py +36 -0
  202. magic_hour/types/params/v1_photo_colorizer_create_body.py +40 -0
  203. magic_hour/types/params/v1_photo_colorizer_create_body_assets.py +33 -0
  204. magic_hour/types/params/v1_photo_colorizer_generate_body_assets.py +17 -0
  205. magic_hour/types/params/v1_text_to_video_create_body.py +78 -0
  206. magic_hour/types/params/v1_text_to_video_create_body_style.py +43 -0
  207. magic_hour/types/params/v1_video_to_video_create_body.py +101 -0
  208. magic_hour/types/params/{post_v1_video_to_video_body_assets.py → v1_video_to_video_create_body_assets.py} +9 -4
  209. magic_hour/types/params/{post_v1_video_to_video_body_style.py → v1_video_to_video_create_body_style.py} +68 -26
  210. magic_hour/types/params/v1_video_to_video_generate_body_assets.py +27 -0
  211. magic_hour-0.44.0.dist-info/METADATA +328 -0
  212. magic_hour-0.44.0.dist-info/RECORD +231 -0
  213. magic_hour/core/__init__.py +0 -52
  214. magic_hour/core/api_error.py +0 -56
  215. magic_hour/core/auth.py +0 -314
  216. magic_hour/core/base_client.py +0 -618
  217. magic_hour/core/binary_response.py +0 -23
  218. magic_hour/core/query.py +0 -106
  219. magic_hour/core/request.py +0 -156
  220. magic_hour/core/response.py +0 -293
  221. magic_hour/core/type_utils.py +0 -28
  222. magic_hour/core/utils.py +0 -55
  223. magic_hour/types/models/post_v1_ai_clothes_changer_response.py +0 -25
  224. magic_hour/types/models/post_v1_ai_headshot_generator_response.py +0 -25
  225. magic_hour/types/models/post_v1_ai_image_generator_response.py +0 -25
  226. magic_hour/types/models/post_v1_ai_image_upscaler_response.py +0 -25
  227. magic_hour/types/models/post_v1_ai_photo_editor_response.py +0 -25
  228. magic_hour/types/models/post_v1_ai_qr_code_generator_response.py +0 -25
  229. magic_hour/types/models/post_v1_animation_response.py +0 -25
  230. magic_hour/types/models/post_v1_face_swap_photo_response.py +0 -25
  231. magic_hour/types/models/post_v1_face_swap_response.py +0 -25
  232. magic_hour/types/models/post_v1_files_upload_urls_response.py +0 -21
  233. magic_hour/types/models/post_v1_image_background_remover_response.py +0 -25
  234. magic_hour/types/models/post_v1_image_to_video_response.py +0 -25
  235. magic_hour/types/models/post_v1_lip_sync_response.py +0 -25
  236. magic_hour/types/models/post_v1_text_to_video_response.py +0 -25
  237. magic_hour/types/models/post_v1_video_to_video_response.py +0 -25
  238. magic_hour/types/params/post_v1_ai_clothes_changer_body.py +0 -40
  239. magic_hour/types/params/post_v1_ai_clothes_changer_body_assets.py +0 -45
  240. magic_hour/types/params/post_v1_ai_headshot_generator_body.py +0 -40
  241. magic_hour/types/params/post_v1_ai_headshot_generator_body_assets.py +0 -28
  242. magic_hour/types/params/post_v1_ai_image_upscaler_body.py +0 -57
  243. magic_hour/types/params/post_v1_ai_image_upscaler_body_assets.py +0 -28
  244. magic_hour/types/params/post_v1_ai_photo_editor_body_assets.py +0 -28
  245. magic_hour/types/params/post_v1_ai_qr_code_generator_body.py +0 -45
  246. magic_hour/types/params/post_v1_face_swap_body.py +0 -72
  247. magic_hour/types/params/post_v1_face_swap_body_assets.py +0 -52
  248. magic_hour/types/params/post_v1_face_swap_photo_body.py +0 -40
  249. magic_hour/types/params/post_v1_face_swap_photo_body_assets.py +0 -36
  250. magic_hour/types/params/post_v1_files_upload_urls_body.py +0 -31
  251. magic_hour/types/params/post_v1_files_upload_urls_body_items_item.py +0 -38
  252. magic_hour/types/params/post_v1_image_background_remover_body.py +0 -40
  253. magic_hour/types/params/post_v1_image_background_remover_body_assets.py +0 -28
  254. magic_hour/types/params/post_v1_image_to_video_body.py +0 -73
  255. magic_hour/types/params/post_v1_image_to_video_body_assets.py +0 -28
  256. magic_hour/types/params/post_v1_image_to_video_body_style.py +0 -37
  257. magic_hour/types/params/post_v1_lip_sync_body.py +0 -80
  258. magic_hour/types/params/post_v1_text_to_video_body.py +0 -57
  259. magic_hour/types/params/post_v1_text_to_video_body_style.py +0 -28
  260. magic_hour/types/params/post_v1_video_to_video_body.py +0 -93
  261. magic_hour-0.9.5.dist-info/METADATA +0 -133
  262. magic_hour-0.9.5.dist-info/RECORD +0 -132
  263. {magic_hour-0.9.5.dist-info → magic_hour-0.44.0.dist-info}/LICENSE +0 -0
  264. {magic_hour-0.9.5.dist-info → magic_hour-0.44.0.dist-info}/WHEEL +0 -0
magic_hour/core/query.py DELETED
@@ -1,106 +0,0 @@
1
- from typing import Any, Dict, Union
2
- from typing_extensions import Literal, Sequence
3
- from urllib.parse import quote_plus, quote
4
-
5
- import httpx
6
-
7
-
8
- # Type alias for query parameters that can handle both primitive data and sequences
9
- QueryParams = Dict[
10
- str, Union[httpx._types.PrimitiveData, Sequence[httpx._types.PrimitiveData]]
11
- ]
12
-
13
-
14
- def encode_query_param(
15
- params: QueryParams,
16
- name: str,
17
- value: Any,
18
- style: Literal["form", "spaceDelimited", "pipeDelimited", "deepObject"] = "form",
19
- explode: bool = True,
20
- ):
21
- if style == "form":
22
- _encode_form(params, name, value, explode)
23
- elif style == "spaceDelimited":
24
- _encode_spaced_delimited(params, name, value, explode)
25
- elif style == "pipeDelimited":
26
- _encode_pipe_delimited(params, name, value, explode)
27
- elif style == "deepObject":
28
- _encode_deep_object(params, name, value, explode)
29
- else:
30
- raise NotImplementedError(f"query param style '{style}' not implemented")
31
-
32
-
33
- def _encode_form(params: QueryParams, name: str, value: Any, explode: bool):
34
- """
35
- Encodes query params in the `form` style as defined by OpenAPI with both explode and non-explode
36
- variants.
37
- """
38
- if isinstance(value, list) and not explode:
39
- # non-explode form lists should be encoded like /users?id=3,4,5
40
- params[name] = quote_plus(",".join(map(str, value)))
41
- elif isinstance(value, dict):
42
- if explode:
43
- # explode form objects should be encoded like /users?key0=val0&key1=val1
44
- # the input param name will be omitted
45
- for k, v in value.items():
46
- params[k] = quote_plus(str(v))
47
- else:
48
- # non-explode form objects should be encoded like /users?id=key0,val0,key1,val1
49
- encoded_chunks = []
50
- for k, v in value.items():
51
- encoded_chunks.extend([str(k), str(v)])
52
- params[name] = quote_plus(",".join(encoded_chunks))
53
- else:
54
- params[name] = value
55
-
56
-
57
- def _encode_spaced_delimited(params: QueryParams, name: str, value: Any, explode: bool):
58
- """
59
- Encodes query params in the `spaceDelimited` style as defined by OpenAPI with both explode and non-explode
60
- variants.
61
- """
62
- if isinstance(value, list) and not explode:
63
- # non-explode spaceDelimited lists should be encoded like /users?id=3%204%205
64
- params[name] = quote(" ".join(map(str, value)))
65
- else:
66
- # according to the docs, spaceDelimited + explode=false only effects lists,
67
- # all other encodings are marked as n/a or are the same as `form` style
68
- # fall back on form style as it is the default for query params
69
- _encode_form(params, name, value, explode)
70
-
71
-
72
- def _encode_pipe_delimited(params: QueryParams, name: str, value: Any, explode: bool):
73
- """
74
- Encodes query params in the `pipeDelimited` style as defined by OpenAPI with both explode and non-explode
75
- variants.
76
- """
77
- if isinstance(value, list) and not explode:
78
- # non-explode pipeDelimited lists should be encoded like /users?id=3|4|5
79
- params[name] = quote("|".join(map(str, value)))
80
- else:
81
- # according to the docs, pipeDelimited + explode=false only effects lists,
82
- # all other encodings are marked as n/a or are the same as `form` style
83
- # fall back on form style as it is the default for query params
84
- _encode_form(params, name, value, explode)
85
-
86
-
87
- def _encode_deep_object(params: QueryParams, name: str, value: Any, explode: bool):
88
- """ """
89
- if isinstance(value, dict):
90
- _encode_deep_object_key(params, name, value)
91
- else:
92
- # according to the docs, deepObject style only applies to
93
- # object encodes, encodings for primitives & arrays are listed as n/a,
94
- # fall back on form style as it is the default for query params
95
- _encode_form(params, name, value, explode)
96
-
97
-
98
- def _encode_deep_object_key(params: QueryParams, key: str, value: Any):
99
- if isinstance(value, dict):
100
- for k, v in value.items():
101
- _encode_deep_object_key(params, f"{key}[{k}]", v)
102
- elif isinstance(value, list):
103
- for i, v in enumerate(value):
104
- _encode_deep_object_key(params, f"{key}[{i}]", v)
105
- else:
106
- params[key] = value
@@ -1,156 +0,0 @@
1
- from typing import Any, Dict, Type, Union, Sequence, List
2
- from urllib.parse import quote_plus
3
-
4
- import httpx
5
- from typing_extensions import TypedDict, Required, NotRequired
6
- from pydantic import TypeAdapter, BaseModel
7
-
8
- from .type_utils import NotGiven
9
- from .query import QueryParams
10
-
11
- """
12
- Request configuration and utility functions for handling HTTP requests.
13
- This module provides type definitions and helper functions for building
14
- and processing HTTP requests in a type-safe manner.
15
- """
16
-
17
-
18
- class RequestConfig(TypedDict):
19
- """
20
- Configuration for HTTP requests.
21
-
22
- Defines all possible parameters that can be passed to an HTTP request,
23
- including required method and URL, as well as optional parameters like
24
- content, headers, authentication, etc.
25
- """
26
-
27
- method: Required[str]
28
- url: Required[httpx._types.URLTypes]
29
- content: NotRequired[httpx._types.RequestContent]
30
- data: NotRequired[httpx._types.RequestData]
31
- files: NotRequired[httpx._types.RequestFiles]
32
- json: NotRequired[Any]
33
- params: NotRequired[QueryParams]
34
- headers: NotRequired[Dict[str, str]]
35
- cookies: NotRequired[Dict[str, str]]
36
- auth: NotRequired[httpx._types.AuthTypes]
37
- follow_redirects: NotRequired[bool]
38
- timeout: NotRequired[httpx._types.TimeoutTypes]
39
- extensions: NotRequired[httpx._types.RequestExtensions]
40
-
41
-
42
- class RequestOptions(TypedDict):
43
- """
44
- Additional options for customizing request behavior.
45
-
46
- Provides configuration for timeouts and additional headers/parameters
47
- that should be included with requests.
48
-
49
- Attributes:
50
- timeout: Number of seconds to await an API call before timing out
51
- additional_headers: Extra headers to include in the request
52
- additional_params: Extra query parameters to include in the request
53
- """
54
-
55
- timeout: NotRequired[int]
56
- additional_headers: NotRequired[Dict[str, str]]
57
- additional_params: NotRequired[QueryParams]
58
-
59
-
60
- def default_request_options() -> RequestOptions:
61
- """
62
- Provides default request options.
63
-
64
- Returns an empty dictionary as the base configuration, allowing defaults
65
- to be handled by the underlying HTTP client.
66
- """
67
- return {}
68
-
69
-
70
- def model_dump(item: Any) -> Any:
71
- """
72
- Recursively converts Pydantic models to dictionaries.
73
-
74
- Handles nested structures including lists and individual models,
75
- preserving alias information and excluding unset values.
76
- """
77
- if isinstance(item, list):
78
- return [model_dump(i) for i in item]
79
- if isinstance(item, BaseModel):
80
- return item.model_dump(exclude_unset=True, by_alias=True)
81
- else:
82
- return item
83
-
84
-
85
- def to_encodable(
86
- *, item: Any, dump_with: Union[Type, Union[Type, Any], List[Type]]
87
- ) -> Any:
88
- """
89
- Validates and converts an item to an encodable format using a specified type.
90
- Uses Pydantic's TypeAdapter for validation and converts the result
91
- to a format suitable for encoding in requests.
92
- """
93
- filtered_item = filter_not_given(item)
94
- adapter: TypeAdapter = TypeAdapter(dump_with)
95
- validated_item = adapter.validate_python(filtered_item)
96
- return model_dump(validated_item)
97
-
98
-
99
- def to_content(*, file: httpx._types.FileTypes) -> httpx._types.RequestContent:
100
- """
101
- Converts the various ways files can be provided to something that is accepted by
102
- the httpx.request content kwarg
103
- """
104
- if isinstance(file, tuple):
105
- file_content: httpx._types.FileContent = file[1]
106
- else:
107
- file_content = file
108
-
109
- if hasattr(file_content, "read") and callable(file_content.read):
110
- return file_content.read()
111
- else:
112
- return file_content
113
-
114
-
115
- def encode_param(
116
- value: Any, explode: bool
117
- ) -> Union[httpx._types.PrimitiveData, Sequence[httpx._types.PrimitiveData]]:
118
- """
119
- Encodes parameter values for use in URLs.
120
-
121
- Handles both simple values and collections, with special handling for
122
- unexploded collections (combining them with commas) versus exploded ones.
123
-
124
- Args:
125
- explode: Whether to explode collections into separate parameters
126
- """
127
- if isinstance(value, (list, dict)) and not explode:
128
- return quote_plus(",".join(map(str, value)))
129
- else:
130
- return value
131
-
132
-
133
- def filter_not_given(value: Any) -> Any:
134
- """Helper function to recursively filter out NotGiven values"""
135
- if isinstance(value, NotGiven):
136
- return None # This will trigger filtering at the container level
137
- elif isinstance(value, dict):
138
- return {
139
- k: filter_not_given(v)
140
- for k, v in value.items()
141
- if not isinstance(v, NotGiven)
142
- }
143
- elif isinstance(value, (list, tuple)):
144
- return type(value)(
145
- filter_not_given(item) for item in value if not isinstance(item, NotGiven)
146
- )
147
- return value
148
-
149
-
150
- def _get_default_for_type(value_type: Any) -> Any:
151
- """Helper to provide appropriate default values for required fields"""
152
- if value_type is dict or isinstance(value_type, dict):
153
- return {}
154
- elif value_type is list or isinstance(value_type, list):
155
- return []
156
- return None
@@ -1,293 +0,0 @@
1
- import json
2
- from typing import Any, Union, Dict, Type, TypeVar, List, Generic, Optional
3
- from pydantic import BaseModel
4
- import httpx
5
-
6
- """
7
- Provides functionality for handling Server-Sent Events (SSE) streams and response data encoding.
8
- Includes utilities for both synchronous and asynchronous stream processing.
9
- """
10
-
11
- EncodableT = TypeVar(
12
- "EncodableT",
13
- bound=Union[
14
- object,
15
- str,
16
- int,
17
- float,
18
- None,
19
- BaseModel,
20
- List[Any],
21
- Dict[str, Any],
22
- ],
23
- )
24
-
25
-
26
- def from_encodable(*, data: Any, load_with: Type[EncodableT]) -> Any:
27
- """
28
- Converts raw data into a specified type using Pydantic validation.
29
-
30
- Uses a dynamic Pydantic model to validate and convert incoming data
31
- into the specified target type.
32
- """
33
-
34
- class Caster(BaseModel):
35
- data: load_with # type: ignore
36
-
37
- return Caster(data=data).data
38
-
39
-
40
- T = TypeVar("T")
41
-
42
-
43
- class StreamResponse(Generic[T]):
44
- """
45
- Handles synchronous streaming of Server-Sent Events (SSE).
46
-
47
- Processes a streaming HTTP response by buffering chunks of data
48
- and parsing them according to SSE format, converting each event
49
- into the specified type.
50
- """
51
-
52
- def __init__(self, response: httpx.Response, stream_context, cast_to: Type[T]):
53
- """
54
- Initialize the stream processor with response and conversion settings.
55
-
56
- Args:
57
- response: The HTTP response containing the SSE stream
58
- stream_context: Context manager for the stream
59
- cast_to: Target type for converting parsed events
60
- """
61
- self.response = response
62
- self._context = stream_context
63
- self.cast_to = cast_to
64
- self.iterator = response.iter_bytes()
65
- self.buffer = bytearray()
66
- self.position = 0
67
-
68
- def __iter__(self):
69
- """Enables iteration over the stream events."""
70
- return self
71
-
72
- def __next__(self) -> T:
73
- """
74
- Retrieves and processes the next event from the stream.
75
-
76
- Buffers incoming data and processes it according to SSE format,
77
- converting each complete event into the specified type.
78
-
79
- Raises:
80
- StopIteration: When the stream is exhausted
81
- """
82
- try:
83
- while True:
84
- event = self._process_buffer()
85
- if event:
86
- return event
87
-
88
- chunk = next(self.iterator)
89
- self.buffer += chunk
90
-
91
- except StopIteration:
92
- event = self._process_buffer(final=True)
93
- if event:
94
- return event
95
- self._context.__exit__(None, None, None)
96
- raise
97
-
98
- def _process_buffer(self, final=False) -> Optional[T]:
99
- """
100
- Processes the current buffer to extract complete SSE events.
101
-
102
- Searches for event boundaries and parses complete events,
103
- handling both JSON and non-JSON payloads.
104
-
105
- Args:
106
- final: Whether this is the final processing of the buffer
107
- """
108
- while self.position < len(self.buffer):
109
- for boundary in [b"\r\n\r\n", b"\n\n", b"\r\r"]:
110
- if (self.position + len(boundary)) <= len(self.buffer):
111
- if (
112
- self.buffer[self.position : self.position + len(boundary)]
113
- == boundary
114
- ):
115
- message = self.buffer[: self.position].decode()
116
- self.buffer = self.buffer[self.position + len(boundary) :]
117
- self.position = 0
118
-
119
- data = self._parse_sse(message)
120
- if data:
121
- try:
122
- parsed_data = json.loads(data)
123
- if (
124
- not isinstance(parsed_data, dict)
125
- or "data" not in parsed_data
126
- ):
127
- parsed_data = {"data": parsed_data}
128
- return from_encodable(
129
- data=parsed_data, load_with=self.cast_to
130
- )
131
- except json.JSONDecodeError:
132
- return from_encodable(
133
- data={"data": data}, load_with=self.cast_to
134
- )
135
- return None
136
-
137
- self.position += 1
138
-
139
- if final and self.buffer:
140
- message = self.buffer.decode()
141
- data = self._parse_sse(message)
142
- if data:
143
- try:
144
- parsed_data = json.loads(data)
145
- if not isinstance(parsed_data, dict) or "data" not in parsed_data:
146
- parsed_data = {"data": parsed_data}
147
- return from_encodable(data=parsed_data, load_with=self.cast_to)
148
- except json.JSONDecodeError:
149
- return from_encodable(data={"data": data}, load_with=self.cast_to)
150
-
151
- return None
152
-
153
- def _parse_sse(self, message: str) -> Optional[str]:
154
- """
155
- Parses an SSE message to extract the data field.
156
-
157
- Handles multi-line data fields and empty data fields according
158
- to the SSE specification.
159
- """
160
- data = []
161
- for line in message.split("\n"):
162
- if line.startswith("data:"):
163
- data.append(line[5:].strip())
164
- elif line.strip() == "data:": # Handle empty data field
165
- data.append("")
166
-
167
- if data:
168
- return "\n".join(data)
169
- return None
170
-
171
-
172
- class AsyncStreamResponse(Generic[T]):
173
- """
174
- Handles asynchronous streaming of Server-Sent Events (SSE).
175
-
176
- Asynchronous version of StreamResponse, providing the same functionality
177
- but compatible with async/await syntax.
178
- """
179
-
180
- def __init__(self, response: httpx.Response, stream_context, cast_to: Type[T]):
181
- """
182
- Initialize the async stream processor.
183
-
184
- Args:
185
- response: The HTTP response containing the SSE stream
186
- stream_context: Async context manager for the stream
187
- cast_to: Target type for converting parsed events
188
- """
189
- self.response = response
190
- self._context = stream_context
191
- self.cast_to = cast_to
192
- self.iterator = response.aiter_bytes()
193
- self.buffer = bytearray()
194
- self.position = 0
195
-
196
- def __aiter__(self):
197
- """Enables async iteration over the stream events."""
198
- return self
199
-
200
- async def __anext__(self) -> T:
201
- """
202
- Asynchronously retrieves and processes the next event from the stream.
203
-
204
- Similar to synchronous version but uses async/await syntax for
205
- iteration and context management.
206
-
207
- Raises:
208
- StopAsyncIteration: When the stream is exhausted
209
- """
210
- try:
211
- while True:
212
- event = self._process_buffer()
213
- if event:
214
- return event
215
-
216
- chunk = await self.iterator.__anext__()
217
- self.buffer += chunk
218
-
219
- except StopAsyncIteration:
220
- event = self._process_buffer(final=True)
221
- if event:
222
- return event
223
- await self._context.__aexit__(None, None, None)
224
- raise
225
-
226
- def _process_buffer(self, final=False) -> Optional[T]:
227
- """
228
- Processes the current buffer to extract complete SSE events.
229
-
230
- Identical to the synchronous version's buffer processing.
231
- """
232
- while self.position < len(self.buffer):
233
- for boundary in [b"\r\n\r\n", b"\n\n", b"\r\r"]:
234
- if (self.position + len(boundary)) <= len(self.buffer):
235
- if (
236
- self.buffer[self.position : self.position + len(boundary)]
237
- == boundary
238
- ):
239
- message = self.buffer[: self.position].decode()
240
- self.buffer = self.buffer[self.position + len(boundary) :]
241
- self.position = 0
242
-
243
- data = self._parse_sse(message)
244
- if data:
245
- try:
246
- parsed_data = json.loads(data)
247
- if (
248
- not isinstance(parsed_data, dict)
249
- or "data" not in parsed_data
250
- ):
251
- parsed_data = {"data": parsed_data}
252
- return from_encodable(
253
- data=parsed_data, load_with=self.cast_to
254
- )
255
- except json.JSONDecodeError:
256
- return from_encodable(
257
- data={"data": data}, load_with=self.cast_to
258
- )
259
- return None
260
-
261
- self.position += 1
262
-
263
- if final and self.buffer:
264
- message = self.buffer.decode()
265
- data = self._parse_sse(message)
266
- if data:
267
- try:
268
- parsed_data = json.loads(data)
269
- if not isinstance(parsed_data, dict) or "data" not in parsed_data:
270
- parsed_data = {"data": parsed_data}
271
- return from_encodable(data=parsed_data, load_with=self.cast_to)
272
- except json.JSONDecodeError:
273
- return from_encodable(data={"data": data}, load_with=self.cast_to)
274
-
275
- return None
276
-
277
- def _parse_sse(self, message: str) -> Optional[str]:
278
- """
279
- Parses an SSE message to extract the data field.
280
-
281
- Identical to the synchronous version's SSE parsing.
282
- """
283
- data = []
284
- for line in message.split("\n"):
285
- line = line.strip()
286
- if line.startswith("data:"):
287
- data.append(line[5:].strip())
288
- elif line == "data:": # Handle empty data field
289
- data.append("")
290
-
291
- if data:
292
- return "\n".join(data)
293
- return None
@@ -1,28 +0,0 @@
1
- from typing import (
2
- Union,
3
- )
4
- from typing_extensions import (
5
- Literal,
6
- TypeVar,
7
- override,
8
- )
9
-
10
- _T = TypeVar("_T")
11
-
12
-
13
- class NotGiven:
14
- """
15
- Used to distinguish omitted keyword arguments from those passed explicitly
16
- with the value None.
17
- """
18
-
19
- def __bool__(self) -> Literal[False]:
20
- return False
21
-
22
- @override
23
- def __repr__(self) -> str:
24
- return "NOT_GIVEN"
25
-
26
-
27
- NotGivenOr = Union[_T, NotGiven]
28
- NOT_GIVEN = NotGiven()
magic_hour/core/utils.py DELETED
@@ -1,55 +0,0 @@
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
@@ -1,25 +0,0 @@
1
- import pydantic
2
-
3
-
4
- class PostV1AiClothesChangerResponse(pydantic.BaseModel):
5
- """
6
- Success
7
- """
8
-
9
- model_config = pydantic.ConfigDict(
10
- arbitrary_types_allowed=True,
11
- populate_by_name=True,
12
- )
13
-
14
- frame_cost: int = pydantic.Field(
15
- alias="frame_cost",
16
- )
17
- """
18
- The frame cost of the image generation
19
- """
20
- id: str = pydantic.Field(
21
- alias="id",
22
- )
23
- """
24
- Unique ID of the image. This value can be used in the [get image project API](https://docs.magichour.ai/api-reference/image-projects/get-image-details) to fetch additional details such as status
25
- """
@@ -1,25 +0,0 @@
1
- import pydantic
2
-
3
-
4
- class PostV1AiHeadshotGeneratorResponse(pydantic.BaseModel):
5
- """
6
- Success
7
- """
8
-
9
- model_config = pydantic.ConfigDict(
10
- arbitrary_types_allowed=True,
11
- populate_by_name=True,
12
- )
13
-
14
- frame_cost: int = pydantic.Field(
15
- alias="frame_cost",
16
- )
17
- """
18
- The frame cost of the image generation
19
- """
20
- id: str = pydantic.Field(
21
- alias="id",
22
- )
23
- """
24
- Unique ID of the image. This value can be used in the [get image project API](https://docs.magichour.ai/api-reference/image-projects/get-image-details) to fetch additional details such as status
25
- """