-
Notifications
You must be signed in to change notification settings - Fork 13
/
models.py
209 lines (166 loc) · 5 KB
/
models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
"""
Models for the data parsed by this module
Each top-level dataclass here has a 'key' property
which determines unique events while merging
"""
from __future__ import annotations
from datetime import datetime
from typing import (
Optional,
Type,
List,
Tuple,
Any,
Union,
Iterator,
Dict,
Protocol,
NamedTuple,
)
from dataclasses import dataclass
from .common import Res
Url = str
def get_union_args(cls: Any) -> Optional[Tuple[Type]]: # type: ignore[type-arg]
if getattr(cls, "__origin__", None) != Union:
return None
args = cls.__args__
args = [e for e in args if e != type(None)] # noqa: E721
assert len(args) > 0
return args # type: ignore
class Subtitles(NamedTuple):
name: str
url: Optional[Url]
class LocationInfo(NamedTuple):
name: Optional[str]
url: Optional[Url]
source: Optional[str]
sourceUrl: Optional[Url]
class BaseEvent(Protocol):
@property
def key(self) -> Any:
...
@dataclass
class Activity(BaseEvent):
header: str
title: str
time: datetime
description: Optional[str]
titleUrl: Optional[Url]
# note: in HTML exports, there is no way to tell the difference between
# a description and a subtitle, so they end up as subtitles
# more lines of text describing this
subtitles: List[Subtitles]
details: List[str]
locationInfos: List[LocationInfo]
products: List[str]
@property
def dt(self) -> datetime:
return self.time
@property
def products_desc(self) -> str:
return ", ".join(sorted(self.products))
@property
def key(self) -> Tuple[str, str, int]:
return self.header, self.title, int(self.time.timestamp())
@dataclass
class YoutubeComment(BaseEvent):
content: str
dt: datetime
urls: List[Url]
@property
def key(self) -> int:
return int(self.dt.timestamp())
@dataclass
class LikedYoutubeVideo(BaseEvent):
title: str
desc: str
link: str
dt: datetime
@property
def key(self) -> int:
return int(self.dt.timestamp())
@dataclass
class PlayStoreAppInstall(BaseEvent):
title: str
dt: datetime
device_name: Optional[str]
@property
def key(self) -> int:
return int(self.dt.timestamp())
@dataclass
class Location(BaseEvent):
lat: float
lng: float
accuracy: Optional[float]
dt: datetime
@property
def key(self) -> Tuple[float, float, Optional[float], int]:
return self.lat, self.lng, self.accuracy, int(self.dt.timestamp())
# this is not cached as a model, its saved as JSON -- its a helper class that placevisit uses
@dataclass
class CandidateLocation:
lat: float
lng: float
address: Optional[str]
name: Optional[str]
placeId: str
locationConfidence: Optional[float] # missing in older (around 2014/15) history
sourceInfoDeviceTag: Optional[int]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> CandidateLocation:
return cls(
address=data.get("address"),
name=data.get("name"),
placeId=data["placeId"],
locationConfidence=data.get("locationConfidence"),
lat=data["latitudeE7"] / 1e7,
lng=data["longitudeE7"] / 1e7,
sourceInfoDeviceTag=data.get("sourceInfo", {}).get("deviceTag"),
)
@dataclass
class PlaceVisit(BaseEvent):
# these are part of the 'location' key
lat: float
lng: float
centerLat: Optional[float]
centerLng: Optional[float]
address: Optional[str]
name: Optional[str]
locationConfidence: Optional[float] # missing in older (around 2014/15) history
placeId: str
startTime: datetime
endTime: datetime
sourceInfoDeviceTag: Optional[int]
otherCandidateLocations: List[CandidateLocation]
# TODO: parse these into an enum of some kind? may be prone to breaking due to new values from google though...
placeConfidence: Optional[str] # older semantic history (pre-2018 didn't have it)
placeVisitType: Optional[str]
visitConfidence: Optional[float] # missing in older (around 2014/15) history
editConfirmationStatus: Optional[str] # missing in older (around 2014/15) history
placeVisitImportance: Optional[str] = None
@property
def dt(self) -> datetime: # type: ignore[override]
return self.startTime
@property
def key(self) -> Tuple[float, float, int, Optional[float]]:
return self.lat, self.lng, int(self.startTime.timestamp()), self.visitConfidence
@dataclass
class ChromeHistory(BaseEvent):
title: str
url: Url
dt: datetime
@property
def key(self) -> Tuple[str, int]:
return self.url, int(self.dt.timestamp())
# can't compute this dynamically -- have to write it out
# if you want to override, override both global variable types with new types
DEFAULT_MODEL_TYPE = Union[
Activity,
LikedYoutubeVideo,
PlayStoreAppInstall,
Location,
ChromeHistory,
YoutubeComment,
PlaceVisit,
]
CacheResults = Iterator[Res[DEFAULT_MODEL_TYPE]]