|
39 | 39 | post = Post("Check this out!", with_attachments=image) |
40 | 40 | """ |
41 | 41 |
|
42 | | -from typing import Union, Optional, List, cast |
| 42 | +from typing import Union, List, cast, Optional, Tuple, Callable, Dict |
43 | 43 | from io import BytesIO |
44 | 44 | import requests |
45 | 45 | from blueskysocial.api_endpoints import UPLOAD_BLOB, RPC_SLUG, IMAGES_TYPE |
46 | 46 | from blueskysocial.post_attachment import PostAttachment |
47 | | -from blueskysocial.utils import get_auth_header |
| 47 | +from blueskysocial.utils import ( |
| 48 | + get_auth_header, |
| 49 | + get_image_aspect_ratio_spec, |
| 50 | + provide_aspect_ratio, |
| 51 | +) |
48 | 52 | from blueskysocial.errors import ImageIsTooLargeError |
49 | 53 | from blueskysocial.typedefs import ApiPayloadType, PostProtocol, as_str |
50 | 54 |
|
@@ -120,7 +124,13 @@ class Image(PostAttachment): |
120 | 124 | post = Post("Photo gallery!", with_attachments=images) |
121 | 125 | """ |
122 | 126 |
|
123 | | - def __init__(self, image: Union[str, BytesIO], alt_text: str) -> None: |
| 127 | + def __init__( |
| 128 | + self, |
| 129 | + image: Union[str, BytesIO], |
| 130 | + alt_text: str, |
| 131 | + aspect_ratio: Optional[Tuple[int, int]] = None, |
| 132 | + require_aspect_ratio: bool = False, |
| 133 | + ) -> None: |
124 | 134 | """ |
125 | 135 | Initialize an Image attachment with source and alternative text. |
126 | 136 |
|
@@ -159,7 +169,9 @@ def __init__(self, image: Union[str, BytesIO], alt_text: str) -> None: |
159 | 169 | image = Image(buffer, alt_text="Chart showing sales data") |
160 | 170 | """ |
161 | 171 | self._alt_text = alt_text |
162 | | - self._image: Optional[bytes] = self._set_image(image) |
| 172 | + self._image = self._set_image(image) |
| 173 | + self._aspect_ratio = aspect_ratio |
| 174 | + self._require_aspect_ratio = require_aspect_ratio |
163 | 175 |
|
164 | 176 | @property |
165 | 177 | def alt_text(self) -> str: |
@@ -380,7 +392,10 @@ def attach_to_post(self, post: PostProtocol, session: ApiPayloadType) -> None: |
380 | 392 | # ] |
381 | 393 | # } |
382 | 394 | """ |
| 395 | + aspect_ratio = provide_aspect_ratio(self) |
383 | 396 | img_dict: ApiPayloadType = {"alt": self.alt_text, "image": self.build(session)} |
| 397 | + if aspect_ratio: |
| 398 | + img_dict["aspectRatio"] = aspect_ratio |
384 | 399 | post.embed["$type"] = IMAGES_TYPE |
385 | 400 | if "images" not in post.embed: |
386 | 401 | post.embed["images"] = [img_dict] |
@@ -444,7 +459,66 @@ def build(self, session: ApiPayloadType) -> ApiPayloadType: |
444 | 459 | resp = requests.post( |
445 | 460 | RPC_SLUG + UPLOAD_BLOB, |
446 | 461 | headers=headers, |
447 | | - data=self._image, |
| 462 | + data=self.data_accessor, |
448 | 463 | ) |
449 | 464 | resp.raise_for_status() |
450 | 465 | return cast(ApiPayloadType, resp.json()["blob"]) |
| 466 | + |
| 467 | + @property |
| 468 | + def data_accessor(self) -> bytes: |
| 469 | + """ |
| 470 | + Get the raw image data in bytes. |
| 471 | +
|
| 472 | +
|
| 473 | + Returns: |
| 474 | + bytes: The raw image data in PNG format ready for upload |
| 475 | +
|
| 476 | + Examples: |
| 477 | + image = Image("photo.jpg", alt_text="A beautiful sunset") |
| 478 | + raw_data = image.data_accessor |
| 479 | + # raw_data contains the bytes of the image |
| 480 | + """ |
| 481 | + return self._image |
| 482 | + |
| 483 | + @property |
| 484 | + def aspect_ratio(self) -> Optional[Tuple[int, int]]: |
| 485 | + """ |
| 486 | + Get the aspect ratio of the image if available. |
| 487 | +
|
| 488 | + Returns the aspect ratio tuple (width, height) if it was provided during |
| 489 | + initialization or calculated. If no aspect ratio is set, returns None. |
| 490 | +
|
| 491 | + Returns: |
| 492 | + Optional[Tuple[int, int]]: The aspect ratio of the image as a tuple |
| 493 | + of (width, height) or None if not set. |
| 494 | +
|
| 495 | + """ |
| 496 | + return self._aspect_ratio |
| 497 | + |
| 498 | + @property |
| 499 | + def require_aspect_ratio(self) -> bool: |
| 500 | + """ |
| 501 | + Check if the image requires an aspect ratio. |
| 502 | +
|
| 503 | + Returns True if the image requires an aspect ratio to be provided, |
| 504 | + otherwise returns False. This is used to determine if aspect ratio |
| 505 | + validation should be enforced during post attachment. |
| 506 | +
|
| 507 | + Returns: |
| 508 | + bool: True if aspect ratio is required, False otherwise. |
| 509 | + """ |
| 510 | + return self._require_aspect_ratio |
| 511 | + |
| 512 | + @property |
| 513 | + def aspect_ratio_function(self) -> Callable[[bytes], Optional[Dict[str, int]]]: |
| 514 | + """ |
| 515 | + Get the function to provide aspect ratio for the image. |
| 516 | +
|
| 517 | + Returns a callable that returns the aspect ratio of the image if available. |
| 518 | + This allows dynamic aspect ratio retrieval based on the current image state. |
| 519 | +
|
| 520 | + Returns: |
| 521 | + callable: A function that returns the aspect ratio tuple (width, height) |
| 522 | + or None if not set. |
| 523 | + """ |
| 524 | + return get_image_aspect_ratio_spec |
0 commit comments