Coverage for src/models/data.py: 43%
90 statements
« prev ^ index » next coverage.py v7.3.0, created at 2025-04-20 12:25 +0000
« prev ^ index » next coverage.py v7.3.0, created at 2025-04-20 12:25 +0000
1import collections
2import json
3import pathlib
4import datetime
5import dataclasses
7from . import Image
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)
17@dataclasses.dataclass
18class Game:
19 """A Game"""
20 slug: str
21 """A slugified version of the name to act as an ID"""
23 description: str
24 """A short description of the game"""
26 image: Image
27 """The game's image (an `Image`)"""
29 url: str
30 """A url link to the game download"""
32 @property
33 def title(self) -> str:
34 """A title cased version of the slug for display."""
35 return self.slug.title()
38@dataclasses.dataclass
39class Spider:
40 """A Spider"""
41 personal: str
42 """The spider's personal name (e.g. *Spidey*)"""
44 common: str
45 """The spider's species common name (e.g. *Mexican Rose Grey*)"""
47 scientific: str
48 """The spider's species scientific name (e.g. *Tlitiocatl verdezi*)"""
50 image: Image
51 """The spider's picture (an `Image`)"""
53 acquired: datetime.datetime
54 """The day the spider was acquired"""
56 deceased: datetime.datetime
57 """The day the spider died"""
59 endemic: str
60 """The spider's natural endemic region (e.g. *Mexico - Southern Guerrero and eastern Oaxaca*)"""
63def load_games(data_dir: str | pathlib.Path, images=[]) -> list[Game]:
64 """Load games from the `data_dir`
66 Games are defined in `data/games.json` in this format:
68 ```json
69 [
70 {
71 "description": "fight a bear",
72 "image": "2025-04-16-bear-fight.png",
73 "slug": "bear-fight",
74 "url": "https://www.bear-fight-game.biz"
75 },
76 ]
77 ```
79 This function converts it into a list of `Game` objects.
81 Pass in site a list of site `Image` objects so the game's image
82 can be associated.
84 ```python
85 games = load_games('./data', images=[])
86 ```
87 """
88 games = []
90 for obj in load_data('games', data_dir):
91 try:
92 image = next((
93 img for img in images if img.filename == obj['image']
94 ))
95 except StopIteration:
96 raise ValueError(f'could not find game image \"{obj["image"]}\"')
98 kwargs = obj
99 kwargs['image'] = image
100 game = Game(**kwargs)
101 games.append(game)
103 return games
106def load_spiders(data_dir: str | pathlib.Path, images=[]) -> list[Spider]:
107 """Load spiders from the `data_dir`.
109 Spiders are defined in `data/spiders.json` in this format:
111 ```json
112 [
113 {
114 "acquired": [
115 5,
116 7,
117 2021
118 ],
119 "common": "Mexican Rose Grey",
120 "deceased": [
121 26,
122 7,
123 2024
124 ],
125 "endemic": "Mexico - Southern Guerrero and eastern Oaxaca",
126 "image": "2023-06-26-spidey.jpg",
127 "personal": "Spidey",
128 "scientific": "Tlitocatl verdezi"
129 }
130 ]
131 ```
132 This function reads the same data and converts it into a list of
133 `Spider` objects in the order they were acquired.
135 Pass in site a list of site `Image` objects so the spider's image
136 can be associated.
138 ```python
139 spiders = load_spiders('./data')
140 ```
141 """
142 spiders = []
144 for obj in load_data('spiders', data_dir):
145 try:
146 image = next((
147 img for img in images if img.filename == obj['image']
148 ))
149 except StopIteration:
150 raise ValueError(f'could not find spider image \"{obj["image"]}\"')
152 kwargs = obj
153 kwargs['image'] = image
154 day, month, year = kwargs['acquired']
155 kwargs['acquired'] = datetime.datetime(year=year, month=month, day=day)
156 if deceased := kwargs.get('deceased'):
157 day, month, year = deceased
158 kwargs['deceased'] = datetime.datetime(
159 year=year, month=month, day=day)
160 else:
161 kwargs['deceased'] = None
162 spider = Spider(**kwargs)
163 spiders.append(spider)
165 spiders.sort(key=lambda s: s.acquired)
166 return spiders
169SpiderStats = collections.namedtuple('SpiderStats', [
170 'count_living',
171 'count_deceased',
172 'oldest_living',
173 'youngest_living',
174 'oldest_deceased',
175 'youngest_deceased',
176])
179def load_spider_stats(spiders: list[Spider]) -> SpiderStats:
180 """Generate stats from a list of `spiders`.
182 Returns a `SpiderStats` containing some miscellaneous
183 stats.
185 ```python
186 stats = load_spider_stats(spiders)
187 ```
188 """
189 stats = {}
191 living = [s for s in spiders if not s.deceased]
192 deceased = [s for s in spiders if s.deceased]
194 stats['count_living'] = f'{len(living):,}'
195 stats['count_deceased'] = f'{len(deceased):,}'
197 today = datetime.datetime.now()
198 living_by_age = list(sorted(living, key=lambda s: today - s.acquired, reverse=True))
199 oldest_living = living_by_age[0]
200 youngest_living = living_by_age[-1]
201 stats['oldest_living'] = f'{oldest_living.personal} ({(today - oldest_living.acquired).days:,} days)'
202 stats['youngest_living'] = f'{youngest_living.personal} ({(today - youngest_living.acquired).days:,} days)'
204 deceased_by_age = list(sorted(deceased, key=lambda s: s.deceased - s.acquired, reverse=True))
205 oldest_deceased = deceased_by_age[0]
206 stats['oldest_deceased'] = f'{oldest_deceased.personal} ({(oldest_deceased.deceased - oldest_deceased.acquired).days:,} days)'
207 youngest_deceased = deceased_by_age[-1]
208 stats['youngest_deceased'] = f'{youngest_deceased.personal} ({(youngest_deceased.deceased - youngest_deceased.acquired).days:,} days)'
209 return SpiderStats(**stats)