google-api-client-wrapper 1.1.2__tar.gz → 1.1.3__tar.gz

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.
Files changed (44) hide show
  1. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/PKG-INFO +1 -1
  2. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_api_client_wrapper.egg-info/PKG-INFO +1 -1
  3. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/calendar/types.py +47 -41
  4. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/gmail/types.py +21 -7
  5. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/pyproject.toml +1 -1
  6. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/LICENSE +0 -0
  7. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/README.md +0 -0
  8. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_api_client_wrapper.egg-info/SOURCES.txt +0 -0
  9. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_api_client_wrapper.egg-info/dependency_links.txt +0 -0
  10. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_api_client_wrapper.egg-info/requires.txt +0 -0
  11. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_api_client_wrapper.egg-info/top_level.txt +0 -0
  12. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/__init__.py +0 -0
  13. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/__init__.py +0 -0
  14. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/calendar/__init__.py +0 -0
  15. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/calendar/api_service.py +0 -0
  16. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/calendar/constants.py +0 -0
  17. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/calendar/exceptions.py +0 -0
  18. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/calendar/query_builder.py +0 -0
  19. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/calendar/utils.py +0 -0
  20. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/drive/__init__.py +0 -0
  21. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/drive/api_service.py +0 -0
  22. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/drive/constants.py +0 -0
  23. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/drive/exceptions.py +0 -0
  24. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/drive/query_builder.py +0 -0
  25. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/drive/types.py +0 -0
  26. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/drive/utils.py +0 -0
  27. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/gmail/__init__.py +0 -0
  28. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/gmail/api_service.py +0 -0
  29. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/gmail/constants.py +0 -0
  30. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/gmail/exceptions.py +0 -0
  31. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/gmail/query_builder.py +0 -0
  32. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/gmail/utils.py +0 -0
  33. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/tasks/__init__.py +0 -0
  34. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/tasks/api_service.py +0 -0
  35. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/tasks/constants.py +0 -0
  36. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/tasks/exceptions.py +0 -0
  37. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/tasks/query_builder.py +0 -0
  38. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/tasks/types.py +0 -0
  39. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/services/tasks/utils.py +0 -0
  40. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/user_client.py +0 -0
  41. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/utils/__init__.py +0 -0
  42. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/utils/datetime.py +0 -0
  43. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/google_client/utils/validation.py +0 -0
  44. {google_api_client_wrapper-1.1.2 → google_api_client_wrapper-1.1.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-api-client-wrapper
3
- Version: 1.1.2
3
+ Version: 1.1.3
4
4
  Summary: A comprehensive Python wrapper for Google APIs, providing clean and intuitive access to Gmail, Google Drive, Google Calendar, and Google Tasks services.
5
5
  Author-email: Dagmawi Molla <dagmawishewadeg@gmail.com>
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-api-client-wrapper
3
- Version: 1.1.2
3
+ Version: 1.1.3
4
4
  Summary: A comprehensive Python wrapper for Google APIs, providing clean and intuitive access to Gmail, Google Drive, Google Calendar, and Google Tasks services.
5
5
  Author-email: Dagmawi Molla <dagmawishewadeg@gmail.com>
6
6
  License: MIT License
@@ -50,9 +50,12 @@ class CalendarEvent(BaseModel):
50
50
  start: Optional[datetime] = Field(None, description="The start time of the event as a datetime object")
51
51
  end: Optional[datetime] = Field(None, description="The end time of the event as a datetime object")
52
52
  html_link: Optional[str] = Field(None, description="A hyperlink to the event on Google Calendar")
53
- attendees: List[Attendee] = Field(default_factory=list, description="A list of Attendee objects representing the people invited to the event")
54
- recurrence: List[str] = Field(default_factory=list, description="A list of strings defining the recurrence rules for the event in RFC 5545 format")
55
- recurring_event_id: Optional[str] = Field(None, description="The ID of the recurring event if this event is part of a series")
53
+ attendees: List[Attendee] = Field(default_factory=list,
54
+ description="A list of Attendee objects representing the people invited to the event")
55
+ recurrence: List[str] = Field(default_factory=list,
56
+ description="A list of strings defining the recurrence rules for the event in RFC 5545 format")
57
+ recurring_event_id: Optional[str] = Field(None,
58
+ description="The ID of the recurring event if this event is part of a series")
56
59
  creator: Optional[str] = Field(None, description="The creator of the event")
57
60
  organizer: Optional[str] = Field(None, description="The organizer of the event")
58
61
  status: Optional[str] = Field("confirmed", description="The status of the event (confirmed, tentative, cancelled)")
@@ -164,7 +167,7 @@ class CalendarEvent(BaseModel):
164
167
  Dictionary representation suitable for API calls.
165
168
  """
166
169
  event_dict = {}
167
-
170
+
168
171
  if self.event_id:
169
172
  event_dict["id"] = self.event_id
170
173
  if self.summary:
@@ -173,6 +176,8 @@ class CalendarEvent(BaseModel):
173
176
  event_dict["description"] = self.description
174
177
  if self.location:
175
178
  event_dict["location"] = self.location
179
+ if self.start:
180
+ event_dict["time"] = convert_datetime_to_readable(self.start, self.end)
176
181
  if self.html_link:
177
182
  event_dict["htmlLink"] = self.html_link
178
183
  if self.recurrence:
@@ -185,10 +190,10 @@ class CalendarEvent(BaseModel):
185
190
  event_dict["organizer"] = self.organizer
186
191
  if self.status:
187
192
  event_dict["status"] = self.status
188
-
193
+
189
194
  if self.attendees:
190
195
  event_dict["attendees"] = [attendee.to_dict() for attendee in self.attendees]
191
-
196
+
192
197
  return event_dict
193
198
 
194
199
  def __repr__(self):
@@ -210,46 +215,45 @@ class TimeSlot(BaseModel):
210
215
  """
211
216
  start: datetime = Field(..., description="Start datetime of the time slot")
212
217
  end: datetime = Field(..., description="End datetime of the time slot")
213
-
218
+
214
219
  def model_post_init(self, __context__: Any):
215
220
  if self.start >= self.end:
216
221
  raise ValueError("Start time must be before end time")
217
-
222
+
218
223
  def duration(self) -> int:
219
224
  """
220
225
  Calculate the duration of the time slot in minutes.
221
-
226
+
222
227
  Returns:
223
228
  Duration in minutes
224
229
  """
225
230
  return int((self.end - self.start).total_seconds() / 60)
226
-
231
+
227
232
  def overlaps_with(self, other: "TimeSlot") -> bool:
228
233
  """
229
234
  Check if this time slot overlaps with another time slot.
230
-
235
+
231
236
  Args:
232
237
  other: Another TimeSlot to check for overlap
233
-
238
+
234
239
  Returns:
235
240
  True if the time slots overlap, False otherwise
236
241
  """
237
242
  return self.start < other.end and self.end > other.start
238
-
243
+
239
244
  def contains_time(self, time_point: datetime) -> bool:
240
245
  """
241
246
  Check if a specific datetime falls within this time slot.
242
-
247
+
243
248
  Args:
244
249
  time_point: Datetime to check
245
-
250
+
246
251
  Returns:
247
252
  True if the time point is within this slot, False otherwise
248
253
  """
249
254
  return self.start <= time_point < self.end
250
-
251
- def __str__(self):
252
255
 
256
+ def __str__(self):
253
257
  return convert_datetime_to_readable(
254
258
  convert_datetime_to_local_timezone(self.start),
255
259
  convert_datetime_to_local_timezone(self.end)
@@ -262,86 +266,88 @@ class FreeBusyResponse(BaseModel):
262
266
  """
263
267
  start: datetime = Field(..., description="Start time of the query")
264
268
  end: datetime = Field(..., description="End time of the query")
265
- calendars: Dict[str, List[TimeSlot]] = Field(default_factory=dict, description="Dictionary mapping calendar IDs to their busy periods")
266
- errors: Dict[str, str] = Field(default_factory=dict, description="Dictionary mapping calendar IDs to any errors encountered")
267
-
269
+ calendars: Dict[str, List[TimeSlot]] = Field(default_factory=dict,
270
+ description="Dictionary mapping calendar IDs to their busy periods")
271
+ errors: Dict[str, str] = Field(default_factory=dict,
272
+ description="Dictionary mapping calendar IDs to any errors encountered")
273
+
268
274
  def get_busy_periods(self, calendar_id: str = "primary") -> List[TimeSlot]:
269
275
  """
270
276
  Get busy periods for a specific calendar.
271
-
277
+
272
278
  Args:
273
279
  calendar_id: Calendar ID to get busy periods for
274
-
280
+
275
281
  Returns:
276
282
  List of TimeSlot objects representing busy periods
277
283
  """
278
284
  return self.calendars.get(calendar_id, [])
279
-
285
+
280
286
  def is_time_free(self, time_point: datetime, calendar_id: str = "primary") -> bool:
281
287
  """
282
288
  Check if a specific time is free in the given calendar.
283
-
289
+
284
290
  Args:
285
291
  time_point: Datetime to check
286
292
  calendar_id: Calendar ID to check
287
-
293
+
288
294
  Returns:
289
295
  True if the time is free, False if busy
290
296
  """
291
297
  if not (self.start <= time_point <= self.end):
292
298
  raise ValueError("Time point is outside the queried range")
293
-
299
+
294
300
  busy_periods = self.get_busy_periods(calendar_id)
295
301
  return not any(period.contains_time(time_point) for period in busy_periods)
296
-
302
+
297
303
  def is_slot_free(self, slot: TimeSlot, calendar_id: str = "primary") -> bool:
298
304
  """
299
305
  Check if an entire time slot is free in the given calendar.
300
-
306
+
301
307
  Args:
302
308
  slot: TimeSlot to check
303
309
  calendar_id: Calendar ID to check
304
-
310
+
305
311
  Returns:
306
312
  True if the entire slot is free, False if any part is busy
307
313
  """
308
314
  busy_periods = self.get_busy_periods(calendar_id)
309
315
  return not any(period.overlaps_with(slot) for period in busy_periods)
310
-
316
+
311
317
  def get_free_slots(self, duration_minutes: int = 60, calendar_id: str = "primary") -> List[TimeSlot]:
312
318
  """
313
319
  Get all free time slots of a specified duration within the queried range.
314
-
320
+
315
321
  Args:
316
322
  duration_minutes: Minimum duration for free slots in minutes
317
323
  calendar_id: Calendar ID to get free slots for
318
-
324
+
319
325
  Returns:
320
326
  List of TimeSlot objects representing available time slots
321
327
  """
322
328
  from ...utils.datetime import current_datetime_local_timezone
323
-
329
+
324
330
  busy_periods = sorted(self.get_busy_periods(calendar_id), key=lambda x: x.start)
325
331
  free_slots = []
326
-
332
+
327
333
  # Start from the beginning of the range or current time (whichever is later)
328
334
  current_time = max(self.start, current_datetime_local_timezone())
329
-
335
+
330
336
  # Check time before first busy period
331
337
  if busy_periods and current_time < busy_periods[0].start:
332
338
  gap_duration = (busy_periods[0].start - current_time).total_seconds() / 60
333
339
  if gap_duration >= duration_minutes:
334
340
  free_slots.append(TimeSlot(start=current_time, end=busy_periods[0].start))
335
-
341
+
336
342
  # Check gaps between busy periods
337
343
  for i in range(len(busy_periods) - 1):
338
344
  gap_start = busy_periods[i].end
339
345
  gap_end = busy_periods[i + 1].start
340
346
  gap_duration = (gap_end - gap_start).total_seconds() / 60
341
-
347
+
342
348
  if gap_duration >= duration_minutes:
343
349
  free_slots.append(TimeSlot(start=gap_start, end=gap_end))
344
-
350
+
345
351
  # Check time after last busy period
346
352
  if busy_periods:
347
353
  gap_start = busy_periods[-1].end
@@ -354,13 +360,13 @@ class FreeBusyResponse(BaseModel):
354
360
  gap_duration = (self.end - current_time).total_seconds() / 60
355
361
  if gap_duration >= duration_minutes:
356
362
  free_slots.append(TimeSlot(start=current_time, end=self.end))
357
-
363
+
358
364
  return free_slots
359
-
365
+
360
366
  def has_errors(self) -> bool:
361
367
  """
362
368
  Check if there were any errors in the freebusy query.
363
-
369
+
364
370
  Returns:
365
371
  True if there were errors, False otherwise
366
372
  """
@@ -1,4 +1,3 @@
1
-
2
1
  from typing import Optional, List
3
2
  from datetime import datetime
4
3
  from pydantic import BaseModel, Field
@@ -14,7 +13,6 @@ class EmailAddress(BaseModel):
14
13
  email: str = Field(..., description="The email address")
15
14
  name: Optional[str] = Field(None, description="The display name")
16
15
 
17
-
18
16
  def to_dict(self) -> dict:
19
17
  """
20
18
  Converts the EmailAddress instance to a dictionary representation.
@@ -42,7 +40,6 @@ class EmailAttachment(BaseModel):
42
40
  attachment_id: str = Field(..., description="The unique identifier for the attachment in Gmail")
43
41
  message_id: str = Field(..., description="The message id of the message the attachment is attached to")
44
42
 
45
-
46
43
  def to_dict(self) -> dict:
47
44
  """
48
45
  Converts the EmailAttachment instance to a dictionary representation.
@@ -87,7 +84,8 @@ class EmailThread(BaseModel):
87
84
  Represents a Gmail thread containing multiple related messages.
88
85
  """
89
86
  thread_id: Optional[str] = Field(None, description="Unique identifier for the thread")
90
- messages: List["EmailMessage"] = Field(default_factory=list, description="List of EmailMessage objects in this thread")
87
+ messages: List["EmailMessage"] = Field(default_factory=list,
88
+ description="List of EmailMessage objects in this thread")
91
89
  snippet: Optional[str] = Field(None, description="A short snippet of the thread content")
92
90
  history_id: Optional[str] = Field(None, description="The history ID of the thread")
93
91
 
@@ -129,7 +127,7 @@ class EmailThread(BaseModel):
129
127
  participants.add((message.sender.email, message.sender.name))
130
128
  for recipient in message.recipients + message.cc_recipients + message.bcc_recipients:
131
129
  participants.add((recipient.email, recipient.name))
132
-
130
+
133
131
  return [EmailAddress(email=email, name=name) for email, name in participants]
134
132
 
135
133
  def __repr__(self):
@@ -159,9 +157,11 @@ class EmailMessage(BaseModel):
159
157
  attachments: List[EmailAttachment] = Field(default_factory=list, description="List of attachments in the email")
160
158
 
161
159
  sender: Optional[EmailAddress] = Field(None, description="The sender's email address information")
162
- recipients: List[EmailAddress] = Field(default_factory=list, description="List of recipient email addresses (To field)")
160
+ recipients: List[EmailAddress] = Field(default_factory=list,
161
+ description="List of recipient email addresses (To field)")
163
162
  cc_recipients: List[EmailAddress] = Field(default_factory=list, description="List of CC recipient email addresses")
164
- bcc_recipients: List[EmailAddress] = Field(default_factory=list, description="List of BCC recipient email addresses")
163
+ bcc_recipients: List[EmailAddress] = Field(default_factory=list,
164
+ description="List of BCC recipient email addresses")
165
165
 
166
166
  date_time: Optional[datetime] = Field(None, description="When the message was sent or received")
167
167
 
@@ -238,6 +238,20 @@ class EmailMessage(BaseModel):
238
238
  """
239
239
  return label in self.labels
240
240
 
241
+ def to_dict(self):
242
+ return {
243
+ "message_id": self.message_id,
244
+ "thread_id": self.thread_id,
245
+ "sender": self.sender.email,
246
+ "recipients": [recipient.email for recipient in self.recipients],
247
+ "date_time": convert_datetime_to_readable(self.date_time),
248
+ "subject": self.subject,
249
+ "labels": self.labels,
250
+ "snippet": self.snippet.encode("ascii", "ignore").decode("ascii"),
251
+ "body": self.get_plain_text_content().encode("ascii", "ignore").decode("ascii"),
252
+ "attachments": [attachment.to_dict() for attachment in self.attachments],
253
+ }
254
+
241
255
  def __repr__(self):
242
256
  return (
243
257
  f"Subject: {self.subject!r}\n"
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "google-api-client-wrapper"
9
- version = "1.1.2"
9
+ version = "1.1.3"
10
10
  description = "A comprehensive Python wrapper for Google APIs, providing clean and intuitive access to Gmail, Google Drive, Google Calendar, and Google Tasks services."
11
11
  readme = {file = "README.md", content-type = "text/markdown"}
12
12
  authors = [{name = "Dagmawi Molla", email = "dagmawishewadeg@gmail.com"}]