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

1"""ImageKit image adapter implementation.""" 

2 

3from contextlib import suppress 

4from typing import Any 

5from uuid import UUID 

6 

7from acb.depends import depends 

8 

9from ._base import ImagesBase, ImagesBaseSettings 

10 

11 

12class ImageKitImagesSettings(ImagesBaseSettings): 

13 """ImageKit-specific settings.""" 

14 

15 public_key: str 

16 private_key: str 

17 endpoint_url: str 

18 upload_folder: str = "fastblocks" 

19 

20 

21class ImageKitImages(ImagesBase): 

22 """ImageKit image adapter implementation.""" 

23 

24 # Required ACB 0.19.0+ metadata 

25 MODULE_ID: UUID = UUID("01937d86-4f2a-7b3c-8d9e-f3b4d3c2b2a2") # Static UUID7 

26 MODULE_STATUS = "stable" 

27 

28 def __init__(self) -> None: 

29 """Initialize ImageKit adapter.""" 

30 super().__init__() 

31 self.settings = ImageKitImagesSettings() 

32 

33 # Register with ACB dependency system 

34 with suppress(Exception): 

35 depends.set(self) 

36 

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}" 

43 

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("/") 

49 

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}") 

64 

65 if transform_parts: 

66 transform_str = ",".join(transform_parts) 

67 return f"{base_url}/tr:{transform_str}/{image_id}" 

68 

69 return f"{base_url}/{image_id}" 

70 

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)) 

73 

74 # Build attributes string 

75 attr_parts = [f'src="{url}"', f'alt="{alt}"'] 

76 

77 for key, value in attributes.items(): 

78 if key in ("width", "height", "class", "id", "style"): 

79 attr_parts.append(f'{key}="{value}"') 

80 

81 # Add lazy loading by default 

82 if "loading" not in attributes: 

83 attr_parts.append('loading="lazy"') 

84 

85 return f"<img {' '.join(attr_parts)}>" 

86 

87 

88ImagesSettings = ImageKitImagesSettings 

89Images = ImageKitImages 

90 

91depends.set(Images, "imagekit") 

92 

93__all__ = ["ImageKitImages", "ImageKitImagesSettings", "Images", "ImagesSettings"]