Coverage for fastblocks / adapters / images / imagekit.py: 43%
53 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-26 03:58 -0800
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-26 03:58 -0800
1"""ImageKit image adapter implementation."""
3from contextlib import suppress
4from typing import Any
5from uuid import UUID
7from acb.depends import depends
9from ._base import ImagesBase, ImagesBaseSettings
12class ImageKitImagesSettings(ImagesBaseSettings):
13 """ImageKit-specific settings."""
15 public_key: str
16 private_key: str
17 endpoint_url: str
18 upload_folder: str = "fastblocks"
21class ImageKitImages(ImagesBase):
22 """ImageKit image adapter implementation."""
24 # Required ACB 0.19.0+ metadata
25 MODULE_ID: UUID = UUID("01937d86-4f2a-7b3c-8d9e-f3b4d3c2b2a2") # Static UUID7
26 MODULE_STATUS = "stable"
28 def __init__(self) -> None:
29 """Initialize ImageKit adapter."""
30 super().__init__()
31 self.settings = ImageKitImagesSettings()
33 # Register with ACB dependency system
34 with suppress(Exception):
35 depends.set(self)
37 async def upload_image(self, file_data: bytes, filename: str) -> str:
38 """Upload image to ImageKit and return file_id."""
39 # Basic implementation - would integrate with imagekit library
40 # For now, return a mock file_id based on filename
41 file_id = filename.rsplit(".", 1)[0] # Remove extension
42 return f"{self.settings.upload_folder}/{file_id}"
44 async def get_image_url(
45 self, image_id: str, transformations: dict[str, Any] | None = None
46 ) -> str:
47 """Generate ImageKit URL with optional transformations."""
48 base_url = self.settings.endpoint_url.rstrip("/")
50 if transformations:
51 # Build transformation string (ImageKit format)
52 transform_parts = []
53 for key, value in transformations.items():
54 if key == "width":
55 transform_parts.append(f"w-{value}")
56 elif key == "height":
57 transform_parts.append(f"h-{value}")
58 elif key == "crop":
59 transform_parts.append(f"c-{value}")
60 elif key == "quality":
61 transform_parts.append(f"q-{value}")
62 elif key == "format":
63 transform_parts.append(f"f-{value}")
65 if transform_parts:
66 transform_str = ",".join(transform_parts)
67 return f"{base_url}/tr:{transform_str}/{image_id}"
69 return f"{base_url}/{image_id}"
71 def get_img_tag(self, image_id: str, alt: str, **attributes: Any) -> str:
72 url = self.get_image_url(image_id, attributes.pop("transformations", None))
74 # Build attributes string
75 attr_parts = [f'src="{url}"', f'alt="{alt}"']
77 for key, value in attributes.items():
78 if key in ("width", "height", "class", "id", "style"):
79 attr_parts.append(f'{key}="{value}"')
81 # Add lazy loading by default
82 if "loading" not in attributes:
83 attr_parts.append('loading="lazy"')
85 return f"<img {' '.join(attr_parts)}>"
88ImagesSettings = ImageKitImagesSettings
89Images = ImageKitImages
91depends.set(Images, "imagekit")
93__all__ = ["ImageKitImages", "ImageKitImagesSettings", "Images", "ImagesSettings"]