magic_hour 0.36.2__py3-none-any.whl → 0.38.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of magic_hour might be problematic. Click here for more details.

Files changed (46) hide show
  1. magic_hour/__init__.py +1 -1
  2. magic_hour/client.py +1 -1
  3. magic_hour/environment.py +1 -1
  4. magic_hour/resources/v1/ai_clothes_changer/client.py +8 -8
  5. magic_hour/resources/v1/ai_face_editor/client.py +8 -8
  6. magic_hour/resources/v1/ai_gif_generator/client.py +26 -11
  7. magic_hour/resources/v1/ai_headshot_generator/client.py +8 -8
  8. magic_hour/resources/v1/ai_image_editor/client.py +8 -8
  9. magic_hour/resources/v1/ai_image_generator/client.py +7 -7
  10. magic_hour/resources/v1/ai_image_upscaler/client.py +8 -8
  11. magic_hour/resources/v1/ai_meme_generator/client.py +7 -7
  12. magic_hour/resources/v1/ai_photo_editor/client.py +8 -8
  13. magic_hour/resources/v1/ai_qr_code_generator/client.py +7 -7
  14. magic_hour/resources/v1/ai_talking_photo/client.py +8 -8
  15. magic_hour/resources/v1/animation/client.py +8 -8
  16. magic_hour/resources/v1/auto_subtitle_generator/client.py +8 -8
  17. magic_hour/resources/v1/client.py +1 -1
  18. magic_hour/resources/v1/face_detection/client.py +5 -5
  19. magic_hour/resources/v1/face_swap/client.py +8 -8
  20. magic_hour/resources/v1/face_swap_photo/client.py +8 -8
  21. magic_hour/resources/v1/files/client.py +1 -1
  22. magic_hour/resources/v1/files/upload_urls/client.py +2 -2
  23. magic_hour/resources/v1/image_background_remover/client.py +8 -8
  24. magic_hour/resources/v1/image_projects/client.py +4 -4
  25. magic_hour/resources/v1/image_to_video/client.py +8 -8
  26. magic_hour/resources/v1/lip_sync/client.py +8 -8
  27. magic_hour/resources/v1/photo_colorizer/client.py +8 -8
  28. magic_hour/resources/v1/text_to_video/client.py +7 -7
  29. magic_hour/resources/v1/video_projects/client.py +4 -4
  30. magic_hour/resources/v1/video_to_video/client.py +8 -8
  31. magic_hour/types/params/v1_ai_gif_generator_create_body.py +10 -0
  32. magic_hour/types/params/v1_ai_talking_photo_create_body_style.py +4 -3
  33. {magic_hour-0.36.2.dist-info → magic_hour-0.38.0.dist-info}/METADATA +2 -5
  34. {magic_hour-0.36.2.dist-info → magic_hour-0.38.0.dist-info}/RECORD +36 -46
  35. magic_hour/core/__init__.py +0 -50
  36. magic_hour/core/api_error.py +0 -56
  37. magic_hour/core/auth.py +0 -354
  38. magic_hour/core/base_client.py +0 -627
  39. magic_hour/core/binary_response.py +0 -23
  40. magic_hour/core/query.py +0 -124
  41. magic_hour/core/request.py +0 -162
  42. magic_hour/core/response.py +0 -297
  43. magic_hour/core/type_utils.py +0 -28
  44. magic_hour/core/utils.py +0 -55
  45. {magic_hour-0.36.2.dist-info → magic_hour-0.38.0.dist-info}/LICENSE +0 -0
  46. {magic_hour-0.36.2.dist-info → magic_hour-0.38.0.dist-info}/WHEEL +0 -0
@@ -1,627 +0,0 @@
1
- from typing import (
2
- Any,
3
- List,
4
- TypeVar,
5
- Dict,
6
- Optional,
7
- Type,
8
- Union,
9
- cast,
10
- )
11
- from typing_extensions import TypeGuard
12
-
13
- import httpx
14
- from pydantic import BaseModel
15
-
16
- from .api_error import ApiError
17
- from .auth import AuthProvider
18
- from .request import RequestConfig, RequestOptions, default_request_options
19
- from .query import QueryParams
20
- from .response import from_encodable, AsyncStreamResponse, StreamResponse
21
- from .utils import get_response_type, filter_binary_response
22
- from .binary_response import BinaryResponse
23
-
24
- NoneType = type(None)
25
- T = TypeVar(
26
- "T",
27
- bound=Union[object, None, str, "BaseModel", List[Any], Dict[str, Any], Any],
28
- )
29
- _DEFAULT_SERVICE_NAME = "__default_service__"
30
-
31
-
32
- class BaseClient:
33
- """Base client class providing core HTTP client functionality.
34
-
35
- Handles authentication, request building, and response processing for HTTP API clients.
36
- Serves as the foundation for both synchronous and asynchronous client implementations.
37
-
38
- Attributes:
39
- _auths: Dictionary mapping auth provider IDs to AuthProvider instances
40
- """
41
-
42
- def __init__(
43
- self,
44
- base_url: Union[str, Dict[str, str]],
45
- auths: Optional[Dict[str, AuthProvider]] = None,
46
- ):
47
- """Initialize the base client"""
48
- self._base_url = (
49
- base_url
50
- if isinstance(base_url, dict)
51
- else {_DEFAULT_SERVICE_NAME: base_url}
52
- )
53
- self._auths: Dict[str, AuthProvider] = auths or {}
54
-
55
- def register_auth(self, auth_id: str, provider: AuthProvider) -> None:
56
- """Register an authentication provider.
57
-
58
- Args:
59
- auth_id: Unique identifier for the auth provider
60
- provider: AuthProvider instance to handle authentication
61
- """
62
- self._auths[auth_id] = provider
63
-
64
- def default_headers(self) -> Dict[str, str]:
65
- """Get default headers for requests.
66
-
67
- Returns:
68
- Dictionary of default headers
69
- """
70
- headers: Dict[str, str] = {
71
- "x-sideko-sdk-language": "Python",
72
- }
73
- return headers
74
-
75
- def build_url(self, path: str, service_name: Optional[str] = None) -> str:
76
- """Build a complete URL by combining base URL and path.
77
-
78
- Args:
79
- path: API endpoint path
80
-
81
- Returns:
82
- Complete URL string
83
- """
84
- base_url = self._base_url.get(service_name or _DEFAULT_SERVICE_NAME, "")
85
- if base_url.endswith("/"):
86
- base_url = base_url[:-1]
87
- if path.startswith("/"):
88
- path = path[1:]
89
-
90
- return f"{base_url}/{path}"
91
-
92
- def _cast_to_raw_response(
93
- self, res: httpx.Response, cast_to: Union[Type[T], Any]
94
- ) -> TypeGuard[T]:
95
- """Determines if the provided cast_to is an httpx.Response"""
96
- try:
97
- return issubclass(cast_to, httpx.Response)
98
- except TypeError:
99
- return False
100
-
101
- def _apply_auth(
102
- self, *, cfg: RequestConfig, auth_names: List[str]
103
- ) -> RequestConfig:
104
- """Apply authentication to the request configuration.
105
-
106
- Args:
107
- cfg: Request configuration to modify
108
- auth_names: List of auth provider IDs to apply
109
-
110
- Returns:
111
- Modified request configuration
112
- """
113
- for auth_name in auth_names:
114
- auth_provider = self._auths.get(auth_name)
115
- if auth_provider is not None:
116
- cfg = auth_provider.add_to_request(cfg)
117
-
118
- return cfg
119
-
120
- def _apply_headers(
121
- self,
122
- *,
123
- cfg: RequestConfig,
124
- opts: RequestOptions,
125
- content_type: Optional[str] = None,
126
- explicit_headers: Optional[Dict[str, str]] = None,
127
- ) -> RequestConfig:
128
- """Apply headers to the request configuration.
129
-
130
- Args:
131
- cfg: Request configuration to modify
132
- opts: Request options containing additional headers
133
- content_type: Optional content type header
134
- explicit_headers: Optional explicitly specified headers
135
-
136
- Returns:
137
- Modified request configuration
138
- """
139
- headers = cfg.get("headers", {})
140
- headers.update(self.default_headers())
141
-
142
- if content_type is not None:
143
- headers["content-type"] = content_type
144
-
145
- if explicit_headers is not None:
146
- headers.update(explicit_headers)
147
-
148
- additional_headers = opts.get("additional_headers", None)
149
- if additional_headers is not None:
150
- headers.update(additional_headers)
151
-
152
- if len(headers) > 0:
153
- cfg["headers"] = headers
154
-
155
- return cfg
156
-
157
- def _apply_query_params(
158
- self,
159
- *,
160
- cfg: RequestConfig,
161
- opts: RequestOptions,
162
- query_params: Optional[QueryParams] = None,
163
- ) -> RequestConfig:
164
- """Apply query parameters to the request configuration.
165
-
166
- Args:
167
- cfg: Request configuration to modify
168
- opts: Request options containing additional parameters
169
- query_params: Optional query parameters to add
170
-
171
- Returns:
172
- Modified request configuration
173
- """
174
- params = cfg.get("params", {})
175
-
176
- if query_params is not None:
177
- params.update(query_params)
178
-
179
- additional_params = opts.get("additional_params", None)
180
- if additional_params is not None:
181
- params.update(additional_params)
182
-
183
- if len(params) > 0:
184
- cfg["params"] = params
185
-
186
- return cfg
187
-
188
- def _apply_timeout(
189
- self,
190
- *,
191
- cfg: RequestConfig,
192
- opts: RequestOptions,
193
- ) -> RequestConfig:
194
- """Apply timeout settings to the request configuration.
195
-
196
- Args:
197
- cfg: Request configuration to modify
198
- opts: Request options containing timeout settings
199
-
200
- Returns:
201
- Modified request configuration
202
- """
203
- timeout = opts.get("timeout", None)
204
-
205
- if timeout is not None:
206
- cfg["timeout"] = timeout
207
-
208
- return cfg
209
-
210
- def _apply_body(
211
- self,
212
- *,
213
- cfg: RequestConfig,
214
- data: Optional[httpx._types.RequestData] = None,
215
- files: Optional[httpx._types.RequestFiles] = None,
216
- json: Optional[Any] = None,
217
- content: Optional[httpx._types.RequestContent] = None,
218
- ) -> RequestConfig:
219
- """Apply request body content to the request configuration.
220
-
221
- Args:
222
- cfg: Request configuration to modify
223
- data: Optional form data
224
- files: Optional files to upload
225
- json: Optional JSON data
226
- content: Optional raw content
227
-
228
- Returns:
229
- Modified request configuration
230
- """
231
- if data is not None:
232
- cfg["data"] = data
233
-
234
- if files is not None:
235
- cfg["files"] = files
236
-
237
- if json is not None:
238
- cfg["json"] = json
239
-
240
- if content is not None:
241
- cfg["content"] = content
242
-
243
- return cfg
244
-
245
- def build_request(
246
- self,
247
- *,
248
- method: str,
249
- path: str,
250
- service_name: Optional[str] = None,
251
- auth_names: Optional[List[str]] = None,
252
- query_params: Optional[QueryParams] = None,
253
- headers: Optional[Dict[str, str]] = None,
254
- data: Optional[httpx._types.RequestData] = None,
255
- files: Optional[httpx._types.RequestFiles] = None,
256
- json: Optional[Any] = None,
257
- content_type: Optional[str] = None,
258
- content: Optional[httpx._types.RequestContent] = None,
259
- request_options: Optional[RequestOptions] = None,
260
- ) -> RequestConfig:
261
- """Build a complete request configuration.
262
-
263
- Args:
264
- method: HTTP method
265
- path: API endpoint path
266
- auth_names: List of auth provider IDs
267
- query_params: Query parameters
268
- headers: Request headers
269
- data: Form data
270
- files: Files to upload
271
- json: JSON data
272
- content_type: Content type header
273
- content: Raw content
274
- request_options: Additional request options
275
-
276
- Returns:
277
- Complete request configuration
278
- """
279
- opts = request_options or default_request_options()
280
- req_cfg: RequestConfig = {
281
- "method": method,
282
- "url": self.build_url(path, service_name=service_name),
283
- }
284
- req_cfg = self._apply_auth(cfg=req_cfg, auth_names=auth_names or [])
285
- req_cfg = self._apply_headers(
286
- cfg=req_cfg, opts=opts, content_type=content_type, explicit_headers=headers
287
- )
288
- req_cfg = self._apply_query_params(
289
- cfg=req_cfg, opts=opts, query_params=query_params
290
- )
291
- req_cfg = self._apply_body(
292
- cfg=req_cfg, data=data, files=files, json=json, content=content
293
- )
294
- req_cfg = self._apply_timeout(cfg=req_cfg, opts=opts)
295
-
296
- return req_cfg
297
-
298
- def process_response(
299
- self,
300
- *,
301
- response: httpx.Response,
302
- cast_to: Union[Type[T], Any],
303
- ) -> T:
304
- """Process an HTTP response and convert it to the desired type.
305
-
306
- Args:
307
- response: HTTP response to process
308
- cast_to: Type to cast the response data to
309
-
310
- Returns:
311
- Processed response data of the specified type
312
-
313
- Raises:
314
- ApiError: If the response indicates an error
315
- """
316
-
317
- if response.status_code == 204 or cast_to == NoneType:
318
- return cast(T, None)
319
- elif cast_to == BinaryResponse:
320
- return cast(
321
- T,
322
- BinaryResponse(content=response.content, headers=response.headers),
323
- )
324
-
325
- response_type = get_response_type(response.headers)
326
-
327
- if response_type == "json":
328
- if cast_to is type(Any):
329
- return response.json() # type: ignore[no-any-return]
330
- return from_encodable( # type: ignore[no-any-return]
331
- data=response.json(), load_with=filter_binary_response(cast_to=cast_to)
332
- )
333
- elif response_type == "text":
334
- return cast(T, response.text)
335
- else:
336
- return cast(
337
- T,
338
- BinaryResponse(content=response.content, headers=response.headers),
339
- )
340
-
341
-
342
- class SyncBaseClient(BaseClient):
343
- """Synchronous HTTP client implementation.
344
-
345
- Provides synchronous HTTP request capabilities building on the base client functionality.
346
- """
347
-
348
- def __init__(
349
- self,
350
- *,
351
- base_url: Union[str, Dict[str, str]],
352
- httpx_client: httpx.Client,
353
- auths: Optional[Dict[str, AuthProvider]] = None,
354
- ):
355
- """Initialize the synchronous client.
356
-
357
- Args:
358
- httpx_client: Synchronous HTTPX client instance
359
- """
360
- super().__init__(base_url=base_url, auths=auths)
361
- self.httpx_client = httpx_client
362
-
363
- def request(
364
- self,
365
- *,
366
- method: str,
367
- path: str,
368
- cast_to: Union[Type[T], Any],
369
- service_name: Optional[str] = None,
370
- auth_names: Optional[List[str]] = None,
371
- query_params: Optional[QueryParams] = None,
372
- headers: Optional[Dict[str, str]] = None,
373
- data: Optional[httpx._types.RequestData] = None,
374
- files: Optional[httpx._types.RequestFiles] = None,
375
- json: Optional[Any] = None,
376
- content_type: Optional[str] = None,
377
- content: Optional[httpx._types.RequestContent] = None,
378
- request_options: Optional[RequestOptions] = None,
379
- ) -> T:
380
- """Make a synchronous HTTP request.
381
-
382
- Args:
383
- method: HTTP method
384
- path: API endpoint path
385
- cast_to: Type to cast the response to
386
- auth_names: List of auth provider IDs
387
- service_name: The name of the API service to make the request to
388
- query_params: Query parameters
389
- headers: Request headers
390
- data: Form data
391
- files: Files to upload
392
- json: JSON data
393
- content_type: Content type header
394
- content: Raw content
395
- request_options: Additional request options
396
-
397
- Returns:
398
- Response data of the specified type
399
-
400
- Raises:
401
- ApiError: If the request fails
402
- """
403
- req_cfg = self.build_request(
404
- method=method,
405
- path=path,
406
- service_name=service_name,
407
- auth_names=auth_names,
408
- query_params=query_params,
409
- headers=headers,
410
- data=data,
411
- files=files,
412
- json=json,
413
- content_type=content_type,
414
- content=content,
415
- request_options=request_options,
416
- )
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
-
425
- return self.process_response(response=response, cast_to=cast_to)
426
-
427
- def stream_request(
428
- self,
429
- *,
430
- method: str,
431
- path: str,
432
- cast_to: Union[Type[T], Any],
433
- service_name: Optional[str] = None,
434
- auth_names: Optional[List[str]] = None,
435
- query_params: Optional[QueryParams] = None,
436
- headers: Optional[Dict[str, str]] = None,
437
- data: Optional[httpx._types.RequestData] = None,
438
- files: Optional[httpx._types.RequestFiles] = None,
439
- json: Optional[Any] = None,
440
- content_type: Optional[str] = None,
441
- content: Optional[httpx._types.RequestContent] = None,
442
- request_options: Optional[RequestOptions] = None,
443
- ) -> StreamResponse[T]:
444
- """Make a streaming synchronous HTTP request.
445
-
446
- Args:
447
- method: HTTP method
448
- path: API endpoint path
449
- cast_to: Type to cast the response to
450
- auth_names: List of auth provider IDs
451
- service_name: The name of the API service to make the request to
452
- query_params: Query parameters
453
- headers: Request headers
454
- data: Form data
455
- files: Files to upload
456
- json: JSON data
457
- content_type: Content type header
458
- content: Raw content
459
- request_options: Additional request options
460
-
461
- Returns:
462
- StreamResponse containing the streaming response
463
-
464
- Raises:
465
- ApiError: If the request fails
466
- """
467
- req_cfg = self.build_request(
468
- method=method,
469
- path=path,
470
- service_name=service_name,
471
- auth_names=auth_names,
472
- query_params=query_params,
473
- headers=headers,
474
- data=data,
475
- files=files,
476
- json=json,
477
- content_type=content_type,
478
- content=content,
479
- request_options=request_options,
480
- )
481
- context = self.httpx_client.stream(**req_cfg)
482
- response = context.__enter__()
483
- return StreamResponse(response, context, cast_to)
484
-
485
-
486
- class AsyncBaseClient(BaseClient):
487
- """Asynchronous HTTP client implementation.
488
-
489
- Provides asynchronous HTTP request capabilities building on the base client functionality.
490
- """
491
-
492
- def __init__(
493
- self,
494
- *,
495
- base_url: Union[str, Dict[str, str]],
496
- httpx_client: httpx.AsyncClient,
497
- auths: Optional[Dict[str, AuthProvider]] = None,
498
- ):
499
- """Initialize the asynchronous client.
500
-
501
- Args:
502
- httpx_client: Asynchronous HTTPX client instance
503
- """
504
- super().__init__(base_url=base_url, auths=auths)
505
- self.httpx_client = httpx_client
506
-
507
- async def request(
508
- self,
509
- *,
510
- method: str,
511
- path: str,
512
- cast_to: Union[Type[T], Any],
513
- service_name: Optional[str] = None,
514
- auth_names: Optional[List[str]] = None,
515
- query_params: Optional[QueryParams] = None,
516
- headers: Optional[Dict[str, str]] = None,
517
- data: Optional[httpx._types.RequestData] = None,
518
- files: Optional[httpx._types.RequestFiles] = None,
519
- json: Optional[Any] = None,
520
- content_type: Optional[str] = None,
521
- content: Optional[httpx._types.RequestContent] = None,
522
- request_options: Optional[RequestOptions] = None,
523
- ) -> T:
524
- """Make an asynchronous HTTP request.
525
-
526
- Args:
527
- method: HTTP method
528
- path: API endpoint path
529
- cast_to: Type to cast the response to
530
- auth_names: List of auth provider IDs
531
- service_name: The name of the API service to make the request to
532
- query_params: Query parameters
533
- headers: Request headers
534
- data: Form data
535
- files: Files to upload
536
- json: JSON data
537
- content_type: Content type header
538
- content: Raw content
539
- request_options: Additional request options
540
-
541
- Returns:
542
- Response data of the specified type
543
-
544
- Raises:
545
- ApiError: If the request fails
546
- """
547
- req_cfg = self.build_request(
548
- method=method,
549
- path=path,
550
- service_name=service_name,
551
- auth_names=auth_names,
552
- query_params=query_params,
553
- headers=headers,
554
- data=data,
555
- files=files,
556
- json=json,
557
- content_type=content_type,
558
- content=content,
559
- request_options=request_options,
560
- )
561
- response = await self.httpx_client.request(**req_cfg)
562
-
563
- if not response.is_success:
564
- raise ApiError(response=response)
565
-
566
- if self._cast_to_raw_response(res=response, cast_to=cast_to):
567
- return response
568
-
569
- return self.process_response(response=response, cast_to=cast_to)
570
-
571
- async def stream_request(
572
- self,
573
- *,
574
- method: str,
575
- path: str,
576
- cast_to: Union[Type[T], Any],
577
- service_name: Optional[str] = None,
578
- auth_names: Optional[List[str]] = None,
579
- query_params: Optional[QueryParams] = None,
580
- headers: Optional[Dict[str, str]] = None,
581
- data: Optional[httpx._types.RequestData] = None,
582
- files: Optional[httpx._types.RequestFiles] = None,
583
- json: Optional[Any] = None,
584
- content_type: Optional[str] = None,
585
- content: Optional[httpx._types.RequestContent] = None,
586
- request_options: Optional[RequestOptions] = None,
587
- ) -> AsyncStreamResponse[T]:
588
- """Make a streaming asynchronous HTTP request.
589
-
590
- Args:
591
- method: HTTP method
592
- path: API endpoint path
593
- cast_to: Type to cast the response to
594
- auth_names: List of auth provider IDs
595
- service_name: The name of the API service to make the request to
596
- query_params: Query parameters
597
- headers: Request headers
598
- data: Form data
599
- files: Files to upload
600
- json: JSON data
601
- content_type: Content type header
602
- content: Raw content
603
- request_options: Additional request options
604
-
605
- Returns:
606
- AsyncStreamResponse containing the streaming response
607
-
608
- Raises:
609
- ApiError: If the request fails
610
- """
611
- req_cfg = self.build_request(
612
- method=method,
613
- path=path,
614
- service_name=service_name,
615
- auth_names=auth_names,
616
- query_params=query_params,
617
- headers=headers,
618
- data=data,
619
- files=files,
620
- json=json,
621
- content_type=content_type,
622
- content=content,
623
- request_options=request_options,
624
- )
625
- context = self.httpx_client.stream(**req_cfg)
626
- response = await context.__aenter__()
627
- return AsyncStreamResponse(response, context, cast_to)
@@ -1,23 +0,0 @@
1
- from httpx._models import Headers
2
-
3
-
4
- class BinaryResponse:
5
- """
6
- Represents a binary HTTP response.
7
-
8
- A lightweight wrapper for binary content and its associated HTTP headers,
9
- typically used for handling file downloads or raw binary data from HTTP requests.
10
- """
11
-
12
- content: bytes
13
- headers: Headers
14
-
15
- def __init__(self, content: bytes, headers: Headers) -> None:
16
- """
17
- Initialize a binary response with content and headers.
18
-
19
- The content represents the raw binary data received in the response,
20
- while headers contain the associated HTTP response headers.
21
- """
22
- self.content = content
23
- self.headers = headers