Coverage for api/utils/image_proxy/wikimedia.py: 29%

18 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 06:14 +0000

1import re 

2from math import inf 

3from urllib.parse import ParseResult 

4 

5from django.conf import settings 

6 

7from api.utils.image_proxy.dataclasses import MediaInfo, RequestConfig 

8 

9 

10# e.g.: 

11# https://upload.wikimedia.org/wikipedia/commons/9/9e/Color_icon_yellow.svg 

12# https://upload.wikimedia.org/wikipedia/commons/f/f3/Open_book_01.svg 

13# The length of the hashes is constant 

14# For each example, the thumbnail URL is: 

15# https://upload.wikimedia.org/wikipedia/commons/thumb/9/9e/Color_icon_yellow.svg/<WIDTH>px-Color_icon_yellow.svg.jpg 

16# https://upload.wikimedia.org/wikipedia/commons/thumb/f/f3/Open_book_01.svg/<WIDTH>px-Open_book_01.svg.jpg 

17# A regex is slightly more reliable for this in case for some reason wikipedia/commons is not a constant prefix 

18# or the filename somehow has an additional `/` in it (though that does not appear to be possible) 

19WIKIMEDIA_PATH_PARSE = re.compile( 

20 r"^(?P<path_prefix>.*?)(?P<hash>/[a-z0-9]{1}/[a-z0-9]{2}/)(?P<filename>.*)$" 

21) 

22 

23 

24def get_wikimedia_thumbnail_url( 

25 media_info: MediaInfo, url: ParseResult, request_config: RequestConfig 

26) -> ParseResult | None: 

27 path_match = WIKIMEDIA_PATH_PARSE.match(url.path) 

28 if path_match is None: 

29 return None 

30 

31 groups = path_match.groupdict() 

32 

33 if request_config.is_full_size: 

34 width = media_info.width or settings.THUMBNAIL_WIDTH_PX 

35 else: 

36 # If not full size, prevent Wikimedia from upscaling the image by using the smaller of 

37 # the default thumbnail size or media_info.width 

38 # Wikimedia _requires_ sending a width, so there's no other fallback than the default thumbnail size 

39 width = min(settings.THUMBNAIL_WIDTH_PX, media_info.width or inf) 

40 

41 params = f"{width}px" 

42 

43 if request_config.is_compressed: 

44 params = f"lossy-{params}" 

45 

46 # always retrieve a PNG, and Site Accelerator will convert it as necessary based on the accept headers 

47 return url._replace( 

48 path=f"{groups['path_prefix']}/thumb{groups['hash']}{groups['filename']}/{params}-{groups['filename']}.png" 

49 )