Coverage for src/models/data.py: 40%

65 statements  

« prev     ^ index     » next       coverage.py v7.3.0, created at 2024-12-21 12:23 +0000

1import collections 

2import json 

3import pathlib 

4import datetime 

5import dataclasses 

6 

7from . import Image 

8 

9 

10def load_data(name, data_dir: str | pathlib.Path): 

11 data_dir = pathlib.Path(data_dir) 

12 data_file = data_dir / f'{name}.json' 

13 with data_file.open('r') as f: 

14 return json.load(f) 

15 

16 

17@dataclasses.dataclass 

18class Spider: 

19 """A Spider""" 

20 personal: str 

21 """The spider's personal name (e.g. *Spidey*)""" 

22 

23 common: str 

24 """The spider's species common name (e.g. *Mexican Rose Grey*)""" 

25 

26 scientific: str 

27 """The spider's species scientific name (e.g. *Tlitiocatl verdezi*)""" 

28 

29 image: Image 

30 """The spider's picture (an `Image`)""" 

31 

32 acquired: datetime.datetime 

33 """The day the spider was acquired""" 

34 

35 deceased: datetime.datetime 

36 """The day the spider died""" 

37 

38 endemic: str 

39 """The spider's natural endemic region (e.g. *Mexico - Southern Guerrero and eastern Oaxaca*)""" 

40 

41 

42def load_spiders(data_dir: str | pathlib.Path, images=[]) -> list[Spider]: 

43 """Load spiders from the `data_dir`. 

44 

45 Spiders are defined in `data/spiders.json` in this format: 

46 

47 ```json 

48 [ 

49 { 

50 "acquired": [ 

51 5, 

52 7, 

53 2021 

54 ], 

55 "common": "Mexican Rose Grey", 

56 "deceased": [ 

57 26, 

58 7, 

59 2024 

60 ], 

61 "endemic": "Mexico - Southern Guerrero and eastern Oaxaca", 

62 "image": "2023-06-26-spidey.jpg", 

63 "personal": "Spidey", 

64 "scientific": "Tlitocatl verdezi" 

65 } 

66 ] 

67 ``` 

68 This function reads the same data and converts it into a list of 

69 `Spider` objects in the order they were acquired. 

70 

71 Pass in site a list of site `Image` objects so the spider's image 

72 can be associated. 

73 

74 ```python 

75 spiders = load_spiders('./data') 

76 ``` 

77 """ 

78 spiders = [] 

79 

80 for obj in load_data('spiders', data_dir): 

81 try: 

82 image = next(( 

83 img for img in images if img.filename == obj['image'] 

84 )) 

85 except StopIteration: 

86 raise ValueError(f'could not find spider image \"{obj["image"]}\"') 

87 

88 kwargs = obj 

89 kwargs['image'] = image 

90 day, month, year = kwargs['acquired'] 

91 kwargs['acquired'] = datetime.datetime(year=year, month=month, day=day) 

92 if deceased := kwargs.get('deceased'): 

93 day, month, year = deceased 

94 kwargs['deceased'] = datetime.datetime( 

95 year=year, month=month, day=day) 

96 else: 

97 kwargs['deceased'] = None 

98 spider = Spider(**kwargs) 

99 spiders.append(spider) 

100 

101 spiders.sort(key=lambda s: s.acquired) 

102 return spiders 

103 

104 

105SpiderStats = collections.namedtuple('SpiderStats', [ 

106 'count_living', 

107 'count_deceased', 

108 'oldest_living', 

109 'youngest_living', 

110 'oldest_deceased', 

111 'youngest_deceased', 

112]) 

113 

114 

115def load_spider_stats(spiders: list[Spider]) -> SpiderStats: 

116 """Generate stats from a list of `spiders`. 

117 

118 Returns a `SpiderStats` containing some miscellaneous 

119 stats. 

120 

121 ```python 

122 stats = load_spider_stats(spiders) 

123 ``` 

124 """ 

125 stats = {} 

126 

127 living = [s for s in spiders if not s.deceased] 

128 deceased = [s for s in spiders if s.deceased] 

129 

130 stats['count_living'] = f'{len(living):,}' 

131 stats['count_deceased'] = f'{len(deceased):,}' 

132 

133 today = datetime.datetime.now() 

134 living_by_age = list(sorted(living, key=lambda s: today - s.acquired, reverse=True)) 

135 oldest_living = living_by_age[0] 

136 youngest_living = living_by_age[-1] 

137 stats['oldest_living'] = f'{oldest_living.personal} ({(today - oldest_living.acquired).days:,} days)' 

138 stats['youngest_living'] = f'{youngest_living.personal} ({(today - youngest_living.acquired).days:,} days)' 

139 

140 deceased_by_age = list(sorted(deceased, key=lambda s: s.deceased - s.acquired, reverse=True)) 

141 oldest_deceased = deceased_by_age[0] 

142 stats['oldest_deceased'] = f'{oldest_deceased.personal} ({(oldest_deceased.deceased - oldest_deceased.acquired).days:,} days)' 

143 youngest_deceased = deceased_by_age[-1] 

144 stats['youngest_deceased'] = f'{youngest_deceased.personal} ({(youngest_deceased.deceased - youngest_deceased.acquired).days:,} days)' 

145 return SpiderStats(**stats)