Coverage for src/models/data.py: 40%
65 statements
« prev ^ index » next coverage.py v7.3.0, created at 2024-11-20 12:27 +0000
« prev ^ index » next coverage.py v7.3.0, created at 2024-11-20 12:27 +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 Spider:
19 """A Spider"""
20 personal: str
21 """The spider's personal name (e.g. *Spidey*)"""
23 common: str
24 """The spider's species common name (e.g. *Mexican Rose Grey*)"""
26 scientific: str
27 """The spider's species scientific name (e.g. *Tlitiocatl verdezi*)"""
29 image: Image
30 """The spider's picture (an `Image`)"""
32 acquired: datetime.datetime
33 """The day the spider was acquired"""
35 deceased: datetime.datetime
36 """The day the spider died"""
38 endemic: str
39 """The spider's natural endemic region (e.g. *Mexico - Southern Guerrero and eastern Oaxaca*)"""
42def load_spiders(data_dir: str | pathlib.Path, images=[]) -> list[Spider]:
43 """Load spiders from the `data_dir`.
45 Spiders are defined in `data/spiders.json` in this format:
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.
71 Pass in site a list of site `Image` objects so the spider's image
72 can be associated.
74 ```python
75 spiders = load_spiders('./data')
76 ```
77 """
78 spiders = []
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"]}\"')
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)
101 spiders.sort(key=lambda s: s.acquired)
102 return spiders
105SpiderStats = collections.namedtuple('SpiderStats', [
106 'count_living',
107 'count_deceased',
108 'oldest_living',
109 'youngest_living',
110 'oldest_deceased',
111 'youngest_deceased',
112])
115def load_spider_stats(spiders: list[Spider]) -> SpiderStats:
116 """Generate stats from a list of `spiders`.
118 Returns a `SpiderStats` containing some miscellaneous
119 stats.
121 ```python
122 stats = load_spider_stats(spiders)
123 ```
124 """
125 stats = {}
127 living = [s for s in spiders if not s.deceased]
128 deceased = [s for s in spiders if s.deceased]
130 stats['count_living'] = f'{len(living):,}'
131 stats['count_deceased'] = f'{len(deceased):,}'
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)'
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)