Coverage for baibu/core/storage_config.py: 100%

25 statements  

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

1"""Build the ``STORAGES`` setting from the environment. 

2 

3Kept free of Django imports so the settings module can call it. 

4 

5Two file stores are configured besides static files: 

6 

7- ``default``: ordinary uploads (Django's ``MEDIA``). 

8- ``private``: contributor files (submitted text, audio, translations). They 

9 are never given a public URL; code reads them through 

10 :mod:`baibu.core.storage`. 

11 

12With ``STORAGE_BACKEND=local`` both live on the local filesystem. With 

13``STORAGE_BACKEND=s3`` both live in one bucket on any S3-compatible server, 

14under the ``media/`` and ``private/`` prefixes. 

15""" 

16 

17from dataclasses import dataclass 

18 

19STORAGE_BACKENDS = ("local", "s3") 

20 

21 

22class StorageConfigError(ValueError): 

23 """Raised when the storage settings are incomplete or contradictory.""" 

24 

25 

26@dataclass(frozen=True) 

27class S3Options: 

28 bucket: str = "" 

29 endpoint_url: str = "" 

30 region: str = "" 

31 access_key_id: str = "" 

32 secret_access_key: str = "" 

33 addressing_style: str = "path" 

34 signed_url_seconds: int = 3600 

35 

36 

37def build_storages( 

38 backend: str, 

39 *, 

40 media_root: str, 

41 private_root: str, 

42 s3: S3Options | None = None, 

43 staticfiles_backend: str = "django.contrib.staticfiles.storage.StaticFilesStorage", 

44) -> dict: 

45 staticfiles = {"BACKEND": staticfiles_backend} 

46 if backend == "local": 

47 return { 

48 "default": { 

49 "BACKEND": "django.core.files.storage.FileSystemStorage", 

50 "OPTIONS": {"location": media_root}, 

51 }, 

52 "private": { 

53 "BACKEND": "baibu.core.storage.PrivateFileSystemStorage", 

54 "OPTIONS": {"location": private_root}, 

55 }, 

56 "staticfiles": staticfiles, 

57 } 

58 if backend == "s3": 

59 s3 = s3 or S3Options() 

60 if not s3.bucket: 

61 msg = "STORAGE_BACKEND=s3 needs S3_BUCKET." 

62 raise StorageConfigError(msg) 

63 options = { 

64 "bucket_name": s3.bucket, 

65 "endpoint_url": s3.endpoint_url or None, 

66 "region_name": s3.region or None, 

67 "access_key": s3.access_key_id or None, 

68 "secret_key": s3.secret_access_key or None, 

69 "addressing_style": s3.addressing_style or None, 

70 # Objects inherit the bucket's access policy; nothing is made public. 

71 "default_acl": None, 

72 "querystring_auth": True, 

73 "querystring_expire": s3.signed_url_seconds, 

74 # Never silently rename: callers choose unique keys. 

75 "file_overwrite": False, 

76 } 

77 return { 

78 "default": {"BACKEND": "storages.backends.s3.S3Storage", "OPTIONS": {**options, "location": "media"}}, 

79 "private": {"BACKEND": "storages.backends.s3.S3Storage", "OPTIONS": {**options, "location": "private"}}, 

80 "staticfiles": staticfiles, 

81 } 

82 msg = f"Unknown STORAGE_BACKEND {backend!r}; expected one of {', '.join(STORAGE_BACKENDS)}." 

83 raise StorageConfigError(msg)