polarsteps-data-parser 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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