Coverage for fastblocks/adapters/images/imagekit.py: 58%

50 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-10-09 00:47 -0700

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

2 

3from contextlib import suppress 

4from typing import Any 

5from uuid import UUID 

6 

7from acb.config import Settings 

8from acb.depends import depends 

9 

10from ._base import ImagesBase 

11 

12 

13class ImageKitSettings(Settings): # type: ignore[misc] 

14 """ImageKit-specific settings.""" 

15 

16 public_key: str 

17 private_key: str 

18 endpoint_url: str 

19 upload_folder: str = "fastblocks" 

20 

21 

22class ImageKitAdapter(ImagesBase): 

23 """ImageKit image adapter implementation.""" 

24 

25 # Required ACB 0.19.0+ metadata 

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

27 MODULE_STATUS = "stable" 

28 

29 def __init__(self) -> None: 

30 """Initialize ImageKit adapter.""" 

31 super().__init__() 

32 self.settings = ImageKitSettings() 

33 

34 # Register with ACB dependency system 

35 with suppress(Exception): 

36 depends.set(self) 

37 

38 async def upload_image(self, file_data: bytes, filename: str) -> str: 

39 """Upload image to ImageKit and return file_id.""" 

40 # Basic implementation - would integrate with imagekit library 

41 # For now, return a mock file_id based on filename 

42 file_id = filename.rsplit(".", 1)[0] # Remove extension 

43 return f"{self.settings.upload_folder}/{file_id}" 

44 

45 async def get_image_url( 

46 self, image_id: str, transformations: dict[str, Any] | None = None 

47 ) -> str: 

48 """Generate ImageKit URL with optional transformations.""" 

49 base_url = self.settings.endpoint_url.rstrip("/") 

50 

51 if transformations: 

52 # Build transformation string (ImageKit format) 

53 transform_parts = [] 

54 for key, value in transformations.items(): 

55 if key == "width": 

56 transform_parts.append(f"w-{value}") 

57 elif key == "height": 

58 transform_parts.append(f"h-{value}") 

59 elif key == "crop": 

60 transform_parts.append(f"c-{value}") 

61 elif key == "quality": 

62 transform_parts.append(f"q-{value}") 

63 elif key == "format": 

64 transform_parts.append(f"f-{value}") 

65 

66 if transform_parts: 

67 transform_str = ",".join(transform_parts) 

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

69 

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

71 

72 def get_img_tag(self, image_id: str, alt: str, **attributes: Any) -> str: 

73 """Generate complete img tag with ImageKit URL.""" 

74 url = self.get_image_url(image_id, attributes.pop("transformations", None)) 

75 

76 # Build attributes string 

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

78 

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

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

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

82 

83 # Add lazy loading by default 

84 if "loading" not in attributes: 

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

86 

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