polarsteps-data-parser 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- polarsteps_data_parser/__main__.py +74 -74
- polarsteps_data_parser/model.py +147 -147
- polarsteps_data_parser/pdf_generator.py +155 -155
- polarsteps_data_parser/retrieve_step_comments.py +135 -135
- polarsteps_data_parser/utils.py +71 -71
- {polarsteps_data_parser-0.1.0.dist-info → polarsteps_data_parser-0.1.1.dist-info}/METADATA +15 -25
- polarsteps_data_parser-0.1.1.dist-info/RECORD +10 -0
- polarsteps_data_parser-0.1.1.dist-info/licenses/LICENSE +21 -0
- polarsteps_data_parser-0.1.0.dist-info/RECORD +0 -10
- polarsteps_data_parser-0.1.0.dist-info/licenses/LICENSE +0 -674
- {polarsteps_data_parser-0.1.0.dist-info → polarsteps_data_parser-0.1.1.dist-info}/WHEEL +0 -0
- {polarsteps_data_parser-0.1.0.dist-info → polarsteps_data_parser-0.1.1.dist-info}/entry_points.txt +0 -0
@@ -1,74 +1,74 @@
|
|
1
|
-
import time
|
2
|
-
from pathlib import Path
|
3
|
-
|
4
|
-
import click
|
5
|
-
from dotenv import load_dotenv
|
6
|
-
|
7
|
-
from polarsteps_data_parser.retrieve_step_comments import StepCommentsEnricher
|
8
|
-
from polarsteps_data_parser.model import Trip, Location
|
9
|
-
from polarsteps_data_parser.pdf_generator import PDFGenerator
|
10
|
-
from polarsteps_data_parser.utils import load_json_from_file
|
11
|
-
|
12
|
-
|
13
|
-
@click.command()
|
14
|
-
@click.argument(
|
15
|
-
"input-folder",
|
16
|
-
type=click.Path(exists=True, dir_okay=True, file_okay=False, readable=True),
|
17
|
-
)
|
18
|
-
@click.option(
|
19
|
-
"--output",
|
20
|
-
default="Trip report.pdf",
|
21
|
-
show_default=True,
|
22
|
-
help="Output PDF file name",
|
23
|
-
)
|
24
|
-
@click.option(
|
25
|
-
"--enrich-with-comments",
|
26
|
-
is_flag=True,
|
27
|
-
show_default=True,
|
28
|
-
default=False,
|
29
|
-
help="Whether to enrich the trip with comments or not.",
|
30
|
-
)
|
31
|
-
def cli(input_folder: str, output: str, enrich_with_comments: str) -> None:
|
32
|
-
"""Parse the data from a Polarsteps trip export.
|
33
|
-
|
34
|
-
INPUT_FOLDER should contain the Polarsteps data export of one (!) trip. Make sure the folder contains
|
35
|
-
a `trip.json` and `locations.json`.
|
36
|
-
"""
|
37
|
-
load_dotenv()
|
38
|
-
|
39
|
-
input_folder = Path(input_folder)
|
40
|
-
trip_data_path = input_folder / "trip.json"
|
41
|
-
location_data_path = input_folder / "locations.json"
|
42
|
-
|
43
|
-
if not trip_data_path.exists() or not location_data_path.exists():
|
44
|
-
log("Error: Cannot find Polarsteps trip in folder!")
|
45
|
-
log("Please make sure the input folder contains a `trip.json` and a `locations.json` file. ")
|
46
|
-
return
|
47
|
-
|
48
|
-
log("✅ Found Polarsteps trip", color="green", bold=True)
|
49
|
-
trip_data = load_json_from_file(trip_data_path)
|
50
|
-
location_data = load_json_from_file(location_data_path)
|
51
|
-
|
52
|
-
log("🔄 Starting to parse trip...", color="cyan")
|
53
|
-
trip = Trip.from_json(trip_data)
|
54
|
-
|
55
|
-
if enrich_with_comments is True:
|
56
|
-
StepCommentsEnricher(input_folder).enrich(trip)
|
57
|
-
|
58
|
-
[Location.from_json(data) for data in location_data["locations"]] # TODO! use location data
|
59
|
-
|
60
|
-
log("🔄 Generating PDF...", color="cyan")
|
61
|
-
pdf_generator = PDFGenerator(output)
|
62
|
-
pdf_generator.generate_pdf(trip)
|
63
|
-
log(f"✅ Generated report: {click.format_filename(output)}", color="green", bold=True)
|
64
|
-
|
65
|
-
|
66
|
-
def log(message: str, color: str = "white", bold: bool = False) -> None:
|
67
|
-
"""Helper function to format messages."""
|
68
|
-
click.echo(click.style(f"[{time.strftime('%H:%M:%S')}] {message}", fg=color, bold=bold))
|
69
|
-
|
70
|
-
|
71
|
-
if __name__ == "__main__":
|
72
|
-
load_dotenv()
|
73
|
-
|
74
|
-
cli()
|
1
|
+
import time
|
2
|
+
from pathlib import Path
|
3
|
+
|
4
|
+
import click
|
5
|
+
from dotenv import load_dotenv
|
6
|
+
|
7
|
+
from polarsteps_data_parser.retrieve_step_comments import StepCommentsEnricher
|
8
|
+
from polarsteps_data_parser.model import Trip, Location
|
9
|
+
from polarsteps_data_parser.pdf_generator import PDFGenerator
|
10
|
+
from polarsteps_data_parser.utils import load_json_from_file
|
11
|
+
|
12
|
+
|
13
|
+
@click.command()
|
14
|
+
@click.argument(
|
15
|
+
"input-folder",
|
16
|
+
type=click.Path(exists=True, dir_okay=True, file_okay=False, readable=True),
|
17
|
+
)
|
18
|
+
@click.option(
|
19
|
+
"--output",
|
20
|
+
default="Trip report.pdf",
|
21
|
+
show_default=True,
|
22
|
+
help="Output PDF file name",
|
23
|
+
)
|
24
|
+
@click.option(
|
25
|
+
"--enrich-with-comments",
|
26
|
+
is_flag=True,
|
27
|
+
show_default=True,
|
28
|
+
default=False,
|
29
|
+
help="Whether to enrich the trip with comments or not.",
|
30
|
+
)
|
31
|
+
def cli(input_folder: str, output: str, enrich_with_comments: str) -> None:
|
32
|
+
"""Parse the data from a Polarsteps trip export.
|
33
|
+
|
34
|
+
INPUT_FOLDER should contain the Polarsteps data export of one (!) trip. Make sure the folder contains
|
35
|
+
a `trip.json` and `locations.json`.
|
36
|
+
"""
|
37
|
+
load_dotenv()
|
38
|
+
|
39
|
+
input_folder = Path(input_folder)
|
40
|
+
trip_data_path = input_folder / "trip.json"
|
41
|
+
location_data_path = input_folder / "locations.json"
|
42
|
+
|
43
|
+
if not trip_data_path.exists() or not location_data_path.exists():
|
44
|
+
log("Error: Cannot find Polarsteps trip in folder!")
|
45
|
+
log("Please make sure the input folder contains a `trip.json` and a `locations.json` file. ")
|
46
|
+
return
|
47
|
+
|
48
|
+
log("✅ Found Polarsteps trip", color="green", bold=True)
|
49
|
+
trip_data = load_json_from_file(trip_data_path)
|
50
|
+
location_data = load_json_from_file(location_data_path)
|
51
|
+
|
52
|
+
log("🔄 Starting to parse trip...", color="cyan")
|
53
|
+
trip = Trip.from_json(trip_data)
|
54
|
+
|
55
|
+
if enrich_with_comments is True:
|
56
|
+
StepCommentsEnricher(input_folder).enrich(trip)
|
57
|
+
|
58
|
+
[Location.from_json(data) for data in location_data["locations"]] # TODO! use location data
|
59
|
+
|
60
|
+
log("🔄 Generating PDF...", color="cyan")
|
61
|
+
pdf_generator = PDFGenerator(output)
|
62
|
+
pdf_generator.generate_pdf(trip)
|
63
|
+
log(f"✅ Generated report: {click.format_filename(output)}", color="green", bold=True)
|
64
|
+
|
65
|
+
|
66
|
+
def log(message: str, color: str = "white", bold: bool = False) -> None:
|
67
|
+
"""Helper function to format messages."""
|
68
|
+
click.echo(click.style(f"[{time.strftime('%H:%M:%S')}] {message}", fg=color, bold=bold))
|
69
|
+
|
70
|
+
|
71
|
+
if __name__ == "__main__":
|
72
|
+
load_dotenv()
|
73
|
+
|
74
|
+
cli()
|
polarsteps_data_parser/model.py
CHANGED
@@ -1,147 +1,147 @@
|
|
1
|
-
from dataclasses import dataclass
|
2
|
-
from datetime import datetime, date
|
3
|
-
from pathlib import Path
|
4
|
-
from typing import Self
|
5
|
-
|
6
|
-
from polarsteps_data_parser.utils import parse_date, find_folder_by_id, list_files_in_folder
|
7
|
-
|
8
|
-
|
9
|
-
@dataclass
|
10
|
-
class Location:
|
11
|
-
"""Location as tracked by the travel tracker."""
|
12
|
-
|
13
|
-
lat: float
|
14
|
-
lon: float
|
15
|
-
time: datetime
|
16
|
-
|
17
|
-
@classmethod
|
18
|
-
def from_json(cls, data: dict) -> Self:
|
19
|
-
"""Parse object from JSON data."""
|
20
|
-
return Location(lat=data["lat"], lon=data["lon"], time=parse_date(data["time"]))
|
21
|
-
|
22
|
-
|
23
|
-
@dataclass
|
24
|
-
class StepLocation:
|
25
|
-
"""Location as provided by a step."""
|
26
|
-
|
27
|
-
lat: float
|
28
|
-
lon: float
|
29
|
-
name: str
|
30
|
-
country: str
|
31
|
-
|
32
|
-
@classmethod
|
33
|
-
def from_json(cls, data: dict) -> Self:
|
34
|
-
"""Parse object from JSON data."""
|
35
|
-
return StepLocation(
|
36
|
-
lat=data["lat"],
|
37
|
-
lon=data["lon"],
|
38
|
-
name=data["name"],
|
39
|
-
country=data["detail"],
|
40
|
-
)
|
41
|
-
|
42
|
-
|
43
|
-
@dataclass
|
44
|
-
class Follower:
|
45
|
-
"""Follower (can leave comments)."""
|
46
|
-
|
47
|
-
user_id: str
|
48
|
-
username: str
|
49
|
-
first_name: str
|
50
|
-
last_name: str
|
51
|
-
|
52
|
-
@classmethod
|
53
|
-
def from_json(cls, data: dict) -> Self:
|
54
|
-
"""Parse object from JSON data."""
|
55
|
-
return Follower(
|
56
|
-
user_id=data["id"],
|
57
|
-
username=data["username"],
|
58
|
-
first_name=data["first_name"],
|
59
|
-
last_name=data["last_name"],
|
60
|
-
)
|
61
|
-
|
62
|
-
@property
|
63
|
-
def name(self) -> str:
|
64
|
-
"""Name of the follower."""
|
65
|
-
return f"{self.first_name} {self.last_name}"
|
66
|
-
|
67
|
-
|
68
|
-
@dataclass
|
69
|
-
class StepComment:
|
70
|
-
"""Comment connected to a step."""
|
71
|
-
|
72
|
-
comment_id: str
|
73
|
-
text: str
|
74
|
-
date: datetime
|
75
|
-
follower: Follower
|
76
|
-
|
77
|
-
@classmethod
|
78
|
-
def from_json(cls, data: dict) -> Self:
|
79
|
-
"""Parse object from JSON data."""
|
80
|
-
return StepComment(
|
81
|
-
comment_id=data["id"],
|
82
|
-
text=data["text"],
|
83
|
-
date=parse_date(data["creation_time"]),
|
84
|
-
follower=Follower.from_json(data["user"]),
|
85
|
-
)
|
86
|
-
|
87
|
-
|
88
|
-
@dataclass
|
89
|
-
class Step:
|
90
|
-
"""Polarsteps Step object."""
|
91
|
-
|
92
|
-
step_id: str
|
93
|
-
name: str
|
94
|
-
description: str
|
95
|
-
location: StepLocation
|
96
|
-
date: date
|
97
|
-
photos: list[Path]
|
98
|
-
videos: list[Path]
|
99
|
-
comments: list[StepComment]
|
100
|
-
|
101
|
-
@classmethod
|
102
|
-
def from_json(cls, data: dict) -> Self:
|
103
|
-
"""Parse object from JSON data."""
|
104
|
-
s = Step(
|
105
|
-
step_id=data["id"],
|
106
|
-
name=data["name"] or data["display_name"],
|
107
|
-
description=data["description"],
|
108
|
-
location=StepLocation.from_json(data["location"]),
|
109
|
-
date=parse_date(data["start_time"]),
|
110
|
-
photos=[],
|
111
|
-
videos=[],
|
112
|
-
comments=[],
|
113
|
-
)
|
114
|
-
s.load_media()
|
115
|
-
return s
|
116
|
-
|
117
|
-
def load_media(self) -> None:
|
118
|
-
"""Load photos and videos for the step."""
|
119
|
-
step_dir = find_folder_by_id(self.step_id)
|
120
|
-
if step_dir is None:
|
121
|
-
self.photos = []
|
122
|
-
self.videos = []
|
123
|
-
else:
|
124
|
-
self.photos = list_files_in_folder(step_dir / "photos", dir_has_to_exist=False)
|
125
|
-
self.videos = list_files_in_folder(step_dir / "videos", dir_has_to_exist=False)
|
126
|
-
|
127
|
-
|
128
|
-
@dataclass
|
129
|
-
class Trip:
|
130
|
-
"""Polarsteps trip object."""
|
131
|
-
|
132
|
-
name: str
|
133
|
-
start_date: datetime
|
134
|
-
end_date: datetime
|
135
|
-
cover_photo_path: str
|
136
|
-
steps: list[Step]
|
137
|
-
|
138
|
-
@classmethod
|
139
|
-
def from_json(cls, data: dict) -> Self:
|
140
|
-
"""Parse object from JSON data."""
|
141
|
-
return Trip(
|
142
|
-
name=data["name"],
|
143
|
-
start_date=parse_date(data.get("start_date")),
|
144
|
-
end_date=parse_date(data.get("end_date")),
|
145
|
-
cover_photo_path=data["cover_photo_path"],
|
146
|
-
steps=[Step.from_json(step) for step in data.get("all_steps")],
|
147
|
-
)
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from datetime import datetime, date
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Self
|
5
|
+
|
6
|
+
from polarsteps_data_parser.utils import parse_date, find_folder_by_id, list_files_in_folder
|
7
|
+
|
8
|
+
|
9
|
+
@dataclass
|
10
|
+
class Location:
|
11
|
+
"""Location as tracked by the travel tracker."""
|
12
|
+
|
13
|
+
lat: float
|
14
|
+
lon: float
|
15
|
+
time: datetime
|
16
|
+
|
17
|
+
@classmethod
|
18
|
+
def from_json(cls, data: dict) -> Self:
|
19
|
+
"""Parse object from JSON data."""
|
20
|
+
return Location(lat=data["lat"], lon=data["lon"], time=parse_date(data["time"]))
|
21
|
+
|
22
|
+
|
23
|
+
@dataclass
|
24
|
+
class StepLocation:
|
25
|
+
"""Location as provided by a step."""
|
26
|
+
|
27
|
+
lat: float
|
28
|
+
lon: float
|
29
|
+
name: str
|
30
|
+
country: str
|
31
|
+
|
32
|
+
@classmethod
|
33
|
+
def from_json(cls, data: dict) -> Self:
|
34
|
+
"""Parse object from JSON data."""
|
35
|
+
return StepLocation(
|
36
|
+
lat=data["lat"],
|
37
|
+
lon=data["lon"],
|
38
|
+
name=data["name"],
|
39
|
+
country=data["detail"],
|
40
|
+
)
|
41
|
+
|
42
|
+
|
43
|
+
@dataclass
|
44
|
+
class Follower:
|
45
|
+
"""Follower (can leave comments)."""
|
46
|
+
|
47
|
+
user_id: str
|
48
|
+
username: str
|
49
|
+
first_name: str
|
50
|
+
last_name: str
|
51
|
+
|
52
|
+
@classmethod
|
53
|
+
def from_json(cls, data: dict) -> Self:
|
54
|
+
"""Parse object from JSON data."""
|
55
|
+
return Follower(
|
56
|
+
user_id=data["id"],
|
57
|
+
username=data["username"],
|
58
|
+
first_name=data["first_name"],
|
59
|
+
last_name=data["last_name"],
|
60
|
+
)
|
61
|
+
|
62
|
+
@property
|
63
|
+
def name(self) -> str:
|
64
|
+
"""Name of the follower."""
|
65
|
+
return f"{self.first_name} {self.last_name}"
|
66
|
+
|
67
|
+
|
68
|
+
@dataclass
|
69
|
+
class StepComment:
|
70
|
+
"""Comment connected to a step."""
|
71
|
+
|
72
|
+
comment_id: str
|
73
|
+
text: str
|
74
|
+
date: datetime
|
75
|
+
follower: Follower
|
76
|
+
|
77
|
+
@classmethod
|
78
|
+
def from_json(cls, data: dict) -> Self:
|
79
|
+
"""Parse object from JSON data."""
|
80
|
+
return StepComment(
|
81
|
+
comment_id=data["id"],
|
82
|
+
text=data["text"],
|
83
|
+
date=parse_date(data["creation_time"]),
|
84
|
+
follower=Follower.from_json(data["user"]),
|
85
|
+
)
|
86
|
+
|
87
|
+
|
88
|
+
@dataclass
|
89
|
+
class Step:
|
90
|
+
"""Polarsteps Step object."""
|
91
|
+
|
92
|
+
step_id: str
|
93
|
+
name: str
|
94
|
+
description: str
|
95
|
+
location: StepLocation
|
96
|
+
date: date
|
97
|
+
photos: list[Path]
|
98
|
+
videos: list[Path]
|
99
|
+
comments: list[StepComment]
|
100
|
+
|
101
|
+
@classmethod
|
102
|
+
def from_json(cls, data: dict) -> Self:
|
103
|
+
"""Parse object from JSON data."""
|
104
|
+
s = Step(
|
105
|
+
step_id=data["id"],
|
106
|
+
name=data["name"] or data["display_name"],
|
107
|
+
description=data["description"],
|
108
|
+
location=StepLocation.from_json(data["location"]),
|
109
|
+
date=parse_date(data["start_time"]),
|
110
|
+
photos=[],
|
111
|
+
videos=[],
|
112
|
+
comments=[],
|
113
|
+
)
|
114
|
+
s.load_media()
|
115
|
+
return s
|
116
|
+
|
117
|
+
def load_media(self) -> None:
|
118
|
+
"""Load photos and videos for the step."""
|
119
|
+
step_dir = find_folder_by_id(self.step_id)
|
120
|
+
if step_dir is None:
|
121
|
+
self.photos = []
|
122
|
+
self.videos = []
|
123
|
+
else:
|
124
|
+
self.photos = list_files_in_folder(step_dir / "photos", dir_has_to_exist=False)
|
125
|
+
self.videos = list_files_in_folder(step_dir / "videos", dir_has_to_exist=False)
|
126
|
+
|
127
|
+
|
128
|
+
@dataclass
|
129
|
+
class Trip:
|
130
|
+
"""Polarsteps trip object."""
|
131
|
+
|
132
|
+
name: str
|
133
|
+
start_date: datetime
|
134
|
+
end_date: datetime
|
135
|
+
cover_photo_path: str
|
136
|
+
steps: list[Step]
|
137
|
+
|
138
|
+
@classmethod
|
139
|
+
def from_json(cls, data: dict) -> Self:
|
140
|
+
"""Parse object from JSON data."""
|
141
|
+
return Trip(
|
142
|
+
name=data["name"],
|
143
|
+
start_date=parse_date(data.get("start_date")),
|
144
|
+
end_date=parse_date(data.get("end_date")),
|
145
|
+
cover_photo_path=data["cover_photo_path"],
|
146
|
+
steps=[Step.from_json(step) for step in data.get("all_steps")],
|
147
|
+
)
|