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.
@@ -1,155 +1,155 @@
1
- from pathlib import Path
2
-
3
- from reportlab.lib.pagesizes import letter
4
- from reportlab.lib.utils import ImageReader
5
- from reportlab.pdfbase.pdfmetrics import stringWidth
6
- from reportlab.pdfgen.canvas import Canvas
7
-
8
- from polarsteps_data_parser.model import Trip, Step
9
- from tqdm import tqdm
10
-
11
-
12
- class PDFGenerator:
13
- """Generates a PDF for Polarsteps Trip objects."""
14
-
15
- MAIN_FONT = ("Helvetica", 12)
16
- BOLD_FONT = ("Helvetica-Bold", 12)
17
- HEADING_FONT = ("Helvetica-Bold", 16)
18
- TITLE_HEADING_FONT = ("Helvetica-Bold", 36)
19
-
20
- def __init__(self, output: str) -> None:
21
- self.filename = output
22
- self.canvas = None
23
- self.width, self.height = letter
24
- self.y_position = self.height - 30
25
-
26
- def generate_pdf(self, trip: Trip) -> None:
27
- """Generate a PDF for a given trip."""
28
- self.canvas = Canvas(self.filename, pagesize=letter)
29
-
30
- self.canvas.setTitle(trip.name)
31
-
32
- self.generate_title_page(trip)
33
-
34
- for i, step in tqdm(enumerate(trip.steps), desc="Generating pages", total=len(trip.steps), ncols=80):
35
- if i == 5:
36
- break
37
- self.generate_step_pages(step)
38
-
39
- self.canvas.save()
40
-
41
- def generate_title_page(self, trip: Trip) -> None:
42
- """Generate title page."""
43
- self.title_heading(trip.name)
44
- self.y_position -= 20
45
- self.short_text(
46
- f"{trip.start_date.strftime('%d-%m-%Y')} - {trip.end_date.strftime('%d-%m-%Y')}", bold=True, centered=True
47
- )
48
- self.photo(trip.cover_photo_path, centered=True, photo_width=400)
49
-
50
- def generate_step_pages(self, step: Step) -> None:
51
- """Add a step to the canvas."""
52
- self.new_page()
53
-
54
- self.heading(step.name)
55
-
56
- self.short_text(f"Location: {step.location.name}, {step.location.country}")
57
- self.short_text(f"Date: {step.date.strftime('%d-%m-%Y')}")
58
-
59
- self.long_text(step.description)
60
-
61
- for comment in step.comments:
62
- self.short_text(comment.follower.name, bold=True)
63
- self.long_text(comment.text)
64
-
65
- for photo in step.photos:
66
- self.photo(photo)
67
-
68
- def new_page(self) -> None:
69
- """Add a new page to the canvas."""
70
- self.canvas.showPage()
71
- self.width, self.height = letter
72
- self.y_position = self.height - 30
73
-
74
- def heading(self, text: str) -> None:
75
- """Add heading to canvas."""
76
- if self.y_position < 50:
77
- self.new_page()
78
- self.canvas.setFont(*self.HEADING_FONT)
79
- self.canvas.drawString(30, self.y_position, text)
80
- self.y_position -= 30
81
-
82
- def title_heading(self, text: str) -> None:
83
- """Add heading to canvas."""
84
- self.y_position -= 100
85
- self.canvas.setFont(*self.TITLE_HEADING_FONT)
86
- self.canvas.drawString(self.calc_width_centered(text, self.TITLE_HEADING_FONT), self.y_position, text)
87
- self.y_position -= 30
88
-
89
- def calc_width_centered(self, text: str, font: tuple) -> float:
90
- """Calculate the width location to center the text."""
91
- return (self.width - stringWidth(text, *font)) / 2.0
92
-
93
- def short_text(self, text: str, bold: bool = False, centered: bool = False) -> None:
94
- """Add short text to canvas."""
95
- if self.y_position < 50:
96
- self.new_page()
97
- font = self.BOLD_FONT if bold else self.MAIN_FONT
98
- self.canvas.setFont(*font)
99
- width = self.calc_width_centered(text, self.MAIN_FONT) if centered else 30
100
- self.canvas.drawString(width, self.y_position, text)
101
- self.y_position -= 20
102
-
103
- def long_text(self, text: str) -> None:
104
- """Add long text to canvas."""
105
- if text is None:
106
- return
107
-
108
- self.y_position -= 10
109
- lines = self.wrap_text(text, self.width - 60)
110
- for line in lines:
111
- if self.y_position < 50:
112
- self.new_page()
113
- self.canvas.setFont(*self.MAIN_FONT)
114
- self.canvas.drawString(30, self.y_position, line)
115
- self.y_position -= 20
116
- self.y_position -= 20
117
-
118
- def photo(self, photo_path: Path | str, centered: bool = False, photo_width: int = 250) -> None:
119
- """Add photo to canvas."""
120
- image = ImageReader(photo_path)
121
- img_width, img_height = image.getSize()
122
- aspect = img_height / float(img_width)
123
- new_height = photo_width * aspect
124
- if self.y_position - new_height < 50:
125
- self.canvas.showPage()
126
- self.y_position = self.height - 30
127
- self.canvas.drawImage(
128
- image,
129
- (self.width - photo_width) / 2.0 if centered else 30,
130
- self.y_position - new_height,
131
- width=photo_width,
132
- height=new_height,
133
- )
134
- self.y_position = self.y_position - new_height - 20
135
-
136
- def wrap_text(self, text: str, max_width: int) -> list:
137
- """Wrap text to fit within max_width."""
138
- self.canvas.setFont(*self.MAIN_FONT)
139
- lines = []
140
- words = text.replace("\n", " <newline> ").split()
141
- current_line = ""
142
- for word in words:
143
- if word == "<newline>":
144
- lines.append(current_line)
145
- current_line = ""
146
- continue
147
-
148
- test_line = f"{current_line} {word}".strip()
149
- if self.canvas.stringWidth(test_line) <= max_width:
150
- current_line = test_line
151
- else:
152
- lines.append(current_line)
153
- current_line = word
154
- lines.append(current_line)
155
- return lines
1
+ from pathlib import Path
2
+
3
+ from reportlab.lib.pagesizes import letter
4
+ from reportlab.lib.utils import ImageReader
5
+ from reportlab.pdfbase.pdfmetrics import stringWidth
6
+ from reportlab.pdfgen.canvas import Canvas
7
+
8
+ from polarsteps_data_parser.model import Trip, Step
9
+ from tqdm import tqdm
10
+
11
+
12
+ class PDFGenerator:
13
+ """Generates a PDF for Polarsteps Trip objects."""
14
+
15
+ MAIN_FONT = ("Helvetica", 12)
16
+ BOLD_FONT = ("Helvetica-Bold", 12)
17
+ HEADING_FONT = ("Helvetica-Bold", 16)
18
+ TITLE_HEADING_FONT = ("Helvetica-Bold", 36)
19
+
20
+ def __init__(self, output: str) -> None:
21
+ self.filename = output
22
+ self.canvas = None
23
+ self.width, self.height = letter
24
+ self.y_position = self.height - 30
25
+
26
+ def generate_pdf(self, trip: Trip) -> None:
27
+ """Generate a PDF for a given trip."""
28
+ self.canvas = Canvas(self.filename, pagesize=letter)
29
+
30
+ self.canvas.setTitle(trip.name)
31
+
32
+ self.generate_title_page(trip)
33
+
34
+ for i, step in tqdm(enumerate(trip.steps), desc="Generating pages", total=len(trip.steps), ncols=80):
35
+ if i == 5:
36
+ break
37
+ self.generate_step_pages(step)
38
+
39
+ self.canvas.save()
40
+
41
+ def generate_title_page(self, trip: Trip) -> None:
42
+ """Generate title page."""
43
+ self.title_heading(trip.name)
44
+ self.y_position -= 20
45
+ self.short_text(
46
+ f"{trip.start_date.strftime('%d-%m-%Y')} - {trip.end_date.strftime('%d-%m-%Y')}", bold=True, centered=True
47
+ )
48
+ self.photo(trip.cover_photo_path, centered=True, photo_width=400)
49
+
50
+ def generate_step_pages(self, step: Step) -> None:
51
+ """Add a step to the canvas."""
52
+ self.new_page()
53
+
54
+ self.heading(step.name)
55
+
56
+ self.short_text(f"Location: {step.location.name}, {step.location.country}")
57
+ self.short_text(f"Date: {step.date.strftime('%d-%m-%Y')}")
58
+
59
+ self.long_text(step.description)
60
+
61
+ for comment in step.comments:
62
+ self.short_text(comment.follower.name, bold=True)
63
+ self.long_text(comment.text)
64
+
65
+ for photo in step.photos:
66
+ self.photo(photo)
67
+
68
+ def new_page(self) -> None:
69
+ """Add a new page to the canvas."""
70
+ self.canvas.showPage()
71
+ self.width, self.height = letter
72
+ self.y_position = self.height - 30
73
+
74
+ def heading(self, text: str) -> None:
75
+ """Add heading to canvas."""
76
+ if self.y_position < 50:
77
+ self.new_page()
78
+ self.canvas.setFont(*self.HEADING_FONT)
79
+ self.canvas.drawString(30, self.y_position, text)
80
+ self.y_position -= 30
81
+
82
+ def title_heading(self, text: str) -> None:
83
+ """Add heading to canvas."""
84
+ self.y_position -= 100
85
+ self.canvas.setFont(*self.TITLE_HEADING_FONT)
86
+ self.canvas.drawString(self.calc_width_centered(text, self.TITLE_HEADING_FONT), self.y_position, text)
87
+ self.y_position -= 30
88
+
89
+ def calc_width_centered(self, text: str, font: tuple) -> float:
90
+ """Calculate the width location to center the text."""
91
+ return (self.width - stringWidth(text, *font)) / 2.0
92
+
93
+ def short_text(self, text: str, bold: bool = False, centered: bool = False) -> None:
94
+ """Add short text to canvas."""
95
+ if self.y_position < 50:
96
+ self.new_page()
97
+ font = self.BOLD_FONT if bold else self.MAIN_FONT
98
+ self.canvas.setFont(*font)
99
+ width = self.calc_width_centered(text, self.MAIN_FONT) if centered else 30
100
+ self.canvas.drawString(width, self.y_position, text)
101
+ self.y_position -= 20
102
+
103
+ def long_text(self, text: str) -> None:
104
+ """Add long text to canvas."""
105
+ if text is None:
106
+ return
107
+
108
+ self.y_position -= 10
109
+ lines = self.wrap_text(text, self.width - 60)
110
+ for line in lines:
111
+ if self.y_position < 50:
112
+ self.new_page()
113
+ self.canvas.setFont(*self.MAIN_FONT)
114
+ self.canvas.drawString(30, self.y_position, line)
115
+ self.y_position -= 20
116
+ self.y_position -= 20
117
+
118
+ def photo(self, photo_path: Path | str, centered: bool = False, photo_width: int = 250) -> None:
119
+ """Add photo to canvas."""
120
+ image = ImageReader(photo_path)
121
+ img_width, img_height = image.getSize()
122
+ aspect = img_height / float(img_width)
123
+ new_height = photo_width * aspect
124
+ if self.y_position - new_height < 50:
125
+ self.canvas.showPage()
126
+ self.y_position = self.height - 30
127
+ self.canvas.drawImage(
128
+ image,
129
+ (self.width - photo_width) / 2.0 if centered else 30,
130
+ self.y_position - new_height,
131
+ width=photo_width,
132
+ height=new_height,
133
+ )
134
+ self.y_position = self.y_position - new_height - 20
135
+
136
+ def wrap_text(self, text: str, max_width: int) -> list:
137
+ """Wrap text to fit within max_width."""
138
+ self.canvas.setFont(*self.MAIN_FONT)
139
+ lines = []
140
+ words = text.replace("\n", " <newline> ").split()
141
+ current_line = ""
142
+ for word in words:
143
+ if word == "<newline>":
144
+ lines.append(current_line)
145
+ current_line = ""
146
+ continue
147
+
148
+ test_line = f"{current_line} {word}".strip()
149
+ if self.canvas.stringWidth(test_line) <= max_width:
150
+ current_line = test_line
151
+ else:
152
+ lines.append(current_line)
153
+ current_line = word
154
+ lines.append(current_line)
155
+ return lines
@@ -1,135 +1,135 @@
1
- import json
2
- import os
3
- from pathlib import Path
4
-
5
- import requests
6
-
7
- from polarsteps_data_parser.model import Trip, StepComment
8
-
9
- # Define the headers used for the request to polarsteps.com
10
- headers = {
11
- "Accept": "*/*",
12
- "Accept-Encoding": "gzip, deflate, br, zstd",
13
- "Accept-Language": "en-NL,en;q=0.9,nl-NL;q=0.8,nl;q=0.7,en-US;q=0.6",
14
- "Connection": "keep-alive",
15
- "Cookie": "", # Will be retrieved from environment variables
16
- "Host": "www.polarsteps.com",
17
- "Polarsteps-Api-Version": "13",
18
- "Sec-Fetch-Dest": "empty",
19
- "Sec-Fetch-Mode": "cors",
20
- "Sec-Fetch-Site": "same-origin",
21
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 "
22
- "Safari/537.36",
23
- "sec-ch-ua": '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
24
- "sec-ch-ua-mobile": "?0",
25
- "sec-ch-ua-platform": '"Windows"',
26
- }
27
-
28
-
29
- class StepCommentsEnricher:
30
- """Enriches steps with comments retrieved using the Polarsteps API."""
31
-
32
- def __init__(self, path: Path) -> None:
33
- self.comment_data_path = path / "comments.json"
34
- headers["Cookie"] = os.getenv("COOKIE")
35
-
36
- def enrich(self, trip: Trip) -> None:
37
- """Enrich trip data with comments.
38
-
39
- Args:
40
- ----
41
- trip: trip data
42
-
43
- """
44
- comment_data = self.retrieve_comments(trip)
45
- self.add_comments_to_steps(trip, comment_data)
46
-
47
- def retrieve_comments(self, trip: Trip) -> dict:
48
- """Retrieve comments from Polarsteps API or local file storage.
49
-
50
- Args:
51
- ----
52
- trip: data of the trip
53
-
54
- Returns:
55
- -------
56
- dict: comment data
57
-
58
- """
59
- # Check if there is comment data and give the option to download/use existing data
60
- if self.comment_data_path.exists():
61
- comment_data = self.load_comments_from_file()
62
- return comment_data
63
-
64
- # Retrieve data from the API
65
- comment_data = {"steps": []}
66
- for step in trip.steps:
67
- comments = self.get_comments_for_step(step.step_id)
68
- comment_data["steps"].append({"id": step.step_id, "comments": comments["comments"]})
69
-
70
- self.write_comments_to_file(comment_data)
71
-
72
- return comment_data
73
-
74
- def write_comments_to_file(self, comment_data: dict) -> None:
75
- """Write comments data to file.
76
-
77
- Args:
78
- ----
79
- comment_data: comment data retrieved from the API
80
-
81
- """
82
- with open(self.comment_data_path, "w") as file:
83
- json.dump(comment_data, file, indent=4)
84
-
85
- def load_comments_from_file(self) -> dict:
86
- """Load comments from data file.
87
-
88
- Returns:
89
- -------
90
- dict: comment data
91
-
92
- """
93
- with open(self.comment_data_path, "r") as file:
94
- return json.load(file)
95
-
96
- @staticmethod
97
- def get_comments_for_step(step_id: str) -> dict:
98
- """Retrieve all comments for a step.
99
-
100
- Args:
101
- ----
102
- step_id: id of the step (e.g. 82089888)
103
-
104
- Returns:
105
- -------
106
- dict: response parsed to JSON
107
-
108
- """
109
- url = f"https://www.polarsteps.com/api/social/steps/{step_id}/comments"
110
-
111
- response = requests.get(url, headers=headers)
112
- response.raise_for_status()
113
-
114
- return response.json()
115
-
116
- @staticmethod
117
- def add_comments_to_steps(trip: Trip, comment_data: dict) -> Trip:
118
- """Parse the comment data to the model.
119
-
120
- Args:
121
- ----
122
- trip: trip data
123
- comment_data: comment data
124
-
125
- Returns:
126
- -------
127
- trip: trip data including comments
128
-
129
- """
130
- for step, comments in zip(trip.steps, comment_data["steps"]):
131
- if step.step_id != comments["id"]:
132
- raise ValueError("Steps in trip and comment data are not in the same order.")
133
- step.comments = [StepComment.from_json(c) for c in comments["comments"]]
134
-
135
- return trip
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+
5
+ import requests
6
+
7
+ from polarsteps_data_parser.model import Trip, StepComment
8
+
9
+ # Define the headers used for the request to polarsteps.com
10
+ headers = {
11
+ "Accept": "*/*",
12
+ "Accept-Encoding": "gzip, deflate, br, zstd",
13
+ "Accept-Language": "en-NL,en;q=0.9,nl-NL;q=0.8,nl;q=0.7,en-US;q=0.6",
14
+ "Connection": "keep-alive",
15
+ "Cookie": "", # Will be retrieved from environment variables
16
+ "Host": "www.polarsteps.com",
17
+ "Polarsteps-Api-Version": "13",
18
+ "Sec-Fetch-Dest": "empty",
19
+ "Sec-Fetch-Mode": "cors",
20
+ "Sec-Fetch-Site": "same-origin",
21
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 "
22
+ "Safari/537.36",
23
+ "sec-ch-ua": '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
24
+ "sec-ch-ua-mobile": "?0",
25
+ "sec-ch-ua-platform": '"Windows"',
26
+ }
27
+
28
+
29
+ class StepCommentsEnricher:
30
+ """Enriches steps with comments retrieved using the Polarsteps API."""
31
+
32
+ def __init__(self, path: Path) -> None:
33
+ self.comment_data_path = path / "comments.json"
34
+ headers["Cookie"] = os.getenv("COOKIE")
35
+
36
+ def enrich(self, trip: Trip) -> None:
37
+ """Enrich trip data with comments.
38
+
39
+ Args:
40
+ ----
41
+ trip: trip data
42
+
43
+ """
44
+ comment_data = self.retrieve_comments(trip)
45
+ self.add_comments_to_steps(trip, comment_data)
46
+
47
+ def retrieve_comments(self, trip: Trip) -> dict:
48
+ """Retrieve comments from Polarsteps API or local file storage.
49
+
50
+ Args:
51
+ ----
52
+ trip: data of the trip
53
+
54
+ Returns:
55
+ -------
56
+ dict: comment data
57
+
58
+ """
59
+ # Check if there is comment data and give the option to download/use existing data
60
+ if self.comment_data_path.exists():
61
+ comment_data = self.load_comments_from_file()
62
+ return comment_data
63
+
64
+ # Retrieve data from the API
65
+ comment_data = {"steps": []}
66
+ for step in trip.steps:
67
+ comments = self.get_comments_for_step(step.step_id)
68
+ comment_data["steps"].append({"id": step.step_id, "comments": comments["comments"]})
69
+
70
+ self.write_comments_to_file(comment_data)
71
+
72
+ return comment_data
73
+
74
+ def write_comments_to_file(self, comment_data: dict) -> None:
75
+ """Write comments data to file.
76
+
77
+ Args:
78
+ ----
79
+ comment_data: comment data retrieved from the API
80
+
81
+ """
82
+ with open(self.comment_data_path, "w") as file:
83
+ json.dump(comment_data, file, indent=4)
84
+
85
+ def load_comments_from_file(self) -> dict:
86
+ """Load comments from data file.
87
+
88
+ Returns:
89
+ -------
90
+ dict: comment data
91
+
92
+ """
93
+ with open(self.comment_data_path, "r") as file:
94
+ return json.load(file)
95
+
96
+ @staticmethod
97
+ def get_comments_for_step(step_id: str) -> dict:
98
+ """Retrieve all comments for a step.
99
+
100
+ Args:
101
+ ----
102
+ step_id: id of the step (e.g. 82089888)
103
+
104
+ Returns:
105
+ -------
106
+ dict: response parsed to JSON
107
+
108
+ """
109
+ url = f"https://www.polarsteps.com/api/social/steps/{step_id}/comments"
110
+
111
+ response = requests.get(url, headers=headers)
112
+ response.raise_for_status()
113
+
114
+ return response.json()
115
+
116
+ @staticmethod
117
+ def add_comments_to_steps(trip: Trip, comment_data: dict) -> Trip:
118
+ """Parse the comment data to the model.
119
+
120
+ Args:
121
+ ----
122
+ trip: trip data
123
+ comment_data: comment data
124
+
125
+ Returns:
126
+ -------
127
+ trip: trip data including comments
128
+
129
+ """
130
+ for step, comments in zip(trip.steps, comment_data["steps"]):
131
+ if step.step_id != comments["id"]:
132
+ raise ValueError("Steps in trip and comment data are not in the same order.")
133
+ step.comments = [StepComment.from_json(c) for c in comments["comments"]]
134
+
135
+ return trip