Coverage for seedboxsync/core/database/models/model.py: 100%

32 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-11 07:37 +0000

1# 

2# Copyright (C) 2015-2026 Guillaume Kulakowski <guillaume@kulakowski.fr> 

3# 

4# For the full copyright and license information, please view the LICENSE 

5# file that was distributed with this source code. 

6# 

7"""Peewee model.""" 

8 

9from abc import abstractmethod 

10import datetime 

11from typing import Any, cast 

12from peewee import Model 

13 

14 

15class SeedboxSyncModel(Model): 

16 """Basemodel from which all other peewee models are derived.""" 

17 

18 

19class SeedboxSyncWithStatsModel(SeedboxSyncModel): 

20 """Basemodel from which all other peewee models are derived.""" 

21 

22 @classmethod 

23 def _get_trend(cls, current: int, previous: int) -> str: 

24 """ 

25 Calculate the percentage change between two values. 

26 

27 Args: 

28 current: Current period value. 

29 previous: Previous equivalent period value. 

30 

31 Returns: 

32 Percentage change formatted with an explicit sign. 

33 """ 

34 if previous == 0: 

35 return "+∞%" if current > 0 else "0.0%" 

36 

37 percentage = ((current - previous) / previous) * 100 

38 return f"{percentage:+.1f}%" 

39 

40 @classmethod 

41 def get_stats(cls) -> dict[str, object]: 

42 """ 

43 Calculate summary download statistics across predefined time periods. 

44 

45 Queries finished downloads to calculate total count, total byte size, 

46 human-readable file sizes, and the percentage trend compared with the 

47 previous equivalent period. 

48 

49 Returns: 

50 dict[str, dict[str, Any]]: A dictionary mapping each period name 

51 ("day", "week", "last24h", "last7d", "last30d", "month", "year") 

52 to a dictionary containing: 

53 - total (int): Total count of finished downloads in the period. 

54 - size (int | None): Total size in bytes. 

55 - human_size (str | None): Formatted size string. 

56 - trend (str): Percentage change compared with the previous 

57 equivalent period (e.g. "+12.5%", "-8.3%", "0.0%"). 

58 """ 

59 now = datetime.datetime.now() 

60 today = now.replace(hour=0, minute=0, second=0, microsecond=0) 

61 

62 periods = { 

63 "day": { 

64 "start": today, 

65 "previous_start": today - datetime.timedelta(days=1), 

66 "previous_end": today, 

67 }, 

68 "week": { 

69 "start": today - datetime.timedelta(days=today.weekday()), 

70 "previous_start": today - datetime.timedelta(days=today.weekday() + 7), 

71 "previous_end": today - datetime.timedelta(days=today.weekday()), 

72 }, 

73 "month": { 

74 "start": today.replace(day=1), 

75 "previous_start": (today.replace(day=1) - datetime.timedelta(days=1)).replace(day=1), 

76 "previous_end": today.replace(day=1), 

77 }, 

78 "year": { 

79 "start": today.replace(month=1, day=1), 

80 "previous_start": today.replace( 

81 year=today.year - 1, 

82 month=1, 

83 day=1, 

84 ), 

85 "previous_end": today.replace(month=1, day=1), 

86 }, 

87 "last24h": { 

88 "start": now - datetime.timedelta(hours=24), 

89 "previous_start": now - datetime.timedelta(hours=48), 

90 "previous_end": now - datetime.timedelta(hours=24), 

91 }, 

92 "last7d": { 

93 "start": now - datetime.timedelta(days=7), 

94 "previous_start": now - datetime.timedelta(days=14), 

95 "previous_end": now - datetime.timedelta(days=7), 

96 }, 

97 "last30d": { 

98 "start": now - datetime.timedelta(days=30), 

99 "previous_start": now - datetime.timedelta(days=60), 

100 "previous_end": now - datetime.timedelta(days=30), 

101 }, 

102 } 

103 

104 data = {} 

105 

106 for name, period in periods.items(): 

107 row = cls._get_period_stats(period["start"], period.get("end", None)) 

108 previous_row = cls._get_period_stats(period["previous_start"], period["previous_end"]) 

109 

110 current_total = int(row["total"] or 0) 

111 previous_total = int(previous_row["total"] or 0) 

112 row["trend_total"] = cls._get_trend(current_total, previous_total) 

113 

114 current_size = int(row["size"] or 0) 

115 previous_size = int(previous_row["size"] or 0) 

116 row["trend_size"] = cls._get_trend(current_size, previous_size) 

117 

118 data[name] = row 

119 

120 return cast(dict[str, Any], data) 

121 

122 @classmethod 

123 @abstractmethod 

124 def _get_period_stats(cls, start: datetime.datetime, end: datetime.datetime | None) -> dict[str, Any]: 

125 """Abstract method to calculate statistics for a given period. Must be implemented by subclasses."""