google-api-client-wrapper 1.1.3__tar.gz → 1.1.4__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.3 → google_api_client_wrapper-1.1.4}/PKG-INFO +1 -1
  2. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_api_client_wrapper.egg-info/PKG-INFO +1 -1
  3. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/calendar/utils.py +69 -68
  4. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/pyproject.toml +1 -1
  5. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/LICENSE +0 -0
  6. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/README.md +0 -0
  7. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_api_client_wrapper.egg-info/SOURCES.txt +0 -0
  8. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_api_client_wrapper.egg-info/dependency_links.txt +0 -0
  9. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_api_client_wrapper.egg-info/requires.txt +0 -0
  10. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_api_client_wrapper.egg-info/top_level.txt +0 -0
  11. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/__init__.py +0 -0
  12. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/__init__.py +0 -0
  13. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/calendar/__init__.py +0 -0
  14. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/calendar/api_service.py +0 -0
  15. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/calendar/constants.py +0 -0
  16. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/calendar/exceptions.py +0 -0
  17. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/calendar/query_builder.py +0 -0
  18. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/calendar/types.py +0 -0
  19. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/drive/__init__.py +0 -0
  20. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/drive/api_service.py +0 -0
  21. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/drive/constants.py +0 -0
  22. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/drive/exceptions.py +0 -0
  23. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/drive/query_builder.py +0 -0
  24. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/drive/types.py +0 -0
  25. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/drive/utils.py +0 -0
  26. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/gmail/__init__.py +0 -0
  27. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/gmail/api_service.py +0 -0
  28. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/gmail/constants.py +0 -0
  29. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/gmail/exceptions.py +0 -0
  30. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/gmail/query_builder.py +0 -0
  31. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/gmail/types.py +0 -0
  32. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/gmail/utils.py +0 -0
  33. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/tasks/__init__.py +0 -0
  34. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/tasks/api_service.py +0 -0
  35. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/tasks/constants.py +0 -0
  36. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/tasks/exceptions.py +0 -0
  37. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/tasks/query_builder.py +0 -0
  38. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/tasks/types.py +0 -0
  39. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/services/tasks/utils.py +0 -0
  40. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/user_client.py +0 -0
  41. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/utils/__init__.py +0 -0
  42. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/utils/datetime.py +0 -0
  43. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/google_client/utils/validation.py +0 -0
  44. {google_api_client_wrapper-1.1.3 → google_api_client_wrapper-1.1.4}/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.3
3
+ Version: 1.1.4
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.3
3
+ Version: 1.1.4
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,8 @@
1
1
  from datetime import datetime
2
2
  from typing import Optional, List, Dict, Any
3
3
 
4
+ import tzlocal
5
+
4
6
  from .types import CalendarEvent, Attendee, TimeSlot, FreeBusyResponse
5
7
  from .constants import (
6
8
  MAX_SUMMARY_LENGTH, MAX_DESCRIPTION_LENGTH, MAX_LOCATION_LENGTH,
@@ -8,7 +10,6 @@ from .constants import (
8
10
  )
9
11
  from ...utils.datetime import convert_datetime_to_iso
10
12
 
11
-
12
13
  # Import from shared utilities
13
14
  from ...utils.validation import is_valid_email, validate_text_field, sanitize_header_value
14
15
 
@@ -22,16 +23,16 @@ def validate_datetime_range(start: Optional[datetime], end: Optional[datetime])
22
23
  def parse_datetime_from_api(datetime_data: Dict[str, Any]) -> Optional[datetime]:
23
24
  """
24
25
  Parse datetime from Google Calendar API response.
25
-
26
+
26
27
  Args:
27
28
  datetime_data: Dictionary containing dateTime or date fields
28
-
29
+
29
30
  Returns:
30
31
  Parsed datetime object or None if parsing fails
31
32
  """
32
33
  if not datetime_data:
33
34
  return None
34
-
35
+
35
36
  try:
36
37
  if datetime_data.get("dateTime"):
37
38
  # Handle timezone-aware datetime
@@ -44,22 +45,22 @@ def parse_datetime_from_api(datetime_data: Dict[str, Any]) -> Optional[datetime]
44
45
  return datetime.strptime(datetime_data["date"], "%Y-%m-%d")
45
46
  except (ValueError, TypeError):
46
47
  pass
47
-
48
+
48
49
  return None
49
50
 
50
51
 
51
52
  def parse_attendees_from_api(attendees_data: List[Dict[str, Any]]) -> List[Attendee]:
52
53
  """
53
54
  Parse attendees from Google Calendar API response.
54
-
55
+
55
56
  Args:
56
57
  attendees_data: List of attendee dictionaries from API
57
-
58
+
58
59
  Returns:
59
60
  List of Attendee objects
60
61
  """
61
62
  attendees = []
62
-
63
+
63
64
  for attendee_data in attendees_data:
64
65
  email = attendee_data.get("email")
65
66
  if email and is_valid_email(email):
@@ -67,7 +68,7 @@ def parse_attendees_from_api(attendees_data: List[Dict[str, Any]]) -> List[Atten
67
68
  response_status = attendee_data.get("responseStatus")
68
69
  if response_status and response_status not in VALID_RESPONSE_STATUSES:
69
70
  response_status = None
70
-
71
+
71
72
  attendees.append(Attendee(
72
73
  email=email,
73
74
  display_name=attendee_data.get("displayName"),
@@ -75,17 +76,17 @@ def parse_attendees_from_api(attendees_data: List[Dict[str, Any]]) -> List[Atten
75
76
  ))
76
77
  except ValueError:
77
78
  pass
78
-
79
+
79
80
  return attendees
80
81
 
81
82
 
82
83
  def from_google_event(google_event: Dict[str, Any]) -> CalendarEvent:
83
84
  """
84
85
  Create a CalendarEvent instance from a Google Calendar API response.
85
-
86
+
86
87
  Args:
87
88
  google_event: Dictionary containing event data from Google Calendar API
88
-
89
+
89
90
  Returns:
90
91
  CalendarEvent instance populated with the data from the dictionary
91
92
  """
@@ -96,31 +97,31 @@ def from_google_event(google_event: Dict[str, Any]) -> CalendarEvent:
96
97
  description = google_event.get("description", "").strip() if google_event.get("description") else None
97
98
  location = google_event.get("location", "").strip() if google_event.get("location") else None
98
99
  html_link = google_event.get("htmlLink")
99
-
100
+
100
101
  # Parse datetimes
101
102
  start = parse_datetime_from_api(google_event.get("start", {}))
102
103
  end = parse_datetime_from_api(google_event.get("end", {}))
103
-
104
+
104
105
  # Parse attendees
105
106
  attendees_data = google_event.get("attendees", [])
106
107
  attendees = parse_attendees_from_api(attendees_data)
107
-
108
+
108
109
  # Parse recurrence
109
110
  recurrence = google_event.get("recurrence", [])
110
111
  recurring_event_id = google_event.get("recurringEventId")
111
-
112
+
112
113
  # Parse creator and organizer
113
114
  creator_data = google_event.get("creator", {})
114
115
  creator = creator_data.get("email") if creator_data else None
115
-
116
+
116
117
  organizer_data = google_event.get("organizer", {})
117
118
  organizer = organizer_data.get("email") if organizer_data else None
118
-
119
+
119
120
  # Parse status
120
121
  status = google_event.get("status", "confirmed")
121
122
  if status not in VALID_EVENT_STATUSES:
122
123
  status = "confirmed"
123
-
124
+
124
125
  # Create and return the event
125
126
  event = CalendarEvent(
126
127
  event_id=event_id,
@@ -137,37 +138,37 @@ def from_google_event(google_event: Dict[str, Any]) -> CalendarEvent:
137
138
  organizer=organizer,
138
139
  status=status
139
140
  )
140
-
141
+
141
142
  return event
142
-
143
+
143
144
  except Exception:
144
145
  raise ValueError("Invalid event data - failed to parse calendar event")
145
146
 
146
147
 
147
148
  def create_event_body(
148
- start: datetime,
149
- end: datetime,
150
- summary: str = None,
151
- description: str = None,
152
- location: str = None,
153
- attendees: List[Attendee] = None,
154
- recurrence: List[str] = None
149
+ start: datetime,
150
+ end: datetime,
151
+ summary: str = None,
152
+ description: str = None,
153
+ location: str = None,
154
+ attendees: List[Attendee] = None,
155
+ recurrence: List[str] = None
155
156
  ) -> Dict[str, Any]:
156
157
  """
157
158
  Create event body dictionary for Google Calendar API.
158
-
159
+
159
160
  Args:
160
161
  start: Event start datetime
161
- end: Event end datetime
162
+ end: Event end datetime
162
163
  summary: Event summary/title
163
164
  description: Event description
164
165
  location: Event location
165
166
  attendees: List of attendees
166
167
  recurrence: List of recurrence rules
167
-
168
+
168
169
  Returns:
169
170
  Dictionary suitable for Calendar API requests
170
-
171
+
171
172
  Raises:
172
173
  ValueError: If required fields are invalid
173
174
  """
@@ -175,19 +176,19 @@ def create_event_body(
175
176
  raise ValueError("Event must have both start and end times")
176
177
  if start >= end:
177
178
  raise ValueError("Event start time must be before end time")
178
-
179
+
179
180
  # Validate text fields
180
181
  validate_text_field(summary, MAX_SUMMARY_LENGTH, "summary")
181
182
  validate_text_field(description, MAX_DESCRIPTION_LENGTH, "description")
182
183
  validate_text_field(location, MAX_LOCATION_LENGTH, "location")
183
-
184
+
184
185
  # Build event body
185
186
  event_body = {
186
187
  'summary': summary or "New Event",
187
- 'start': {'dateTime': convert_datetime_to_iso(start)},
188
- 'end': {'dateTime': convert_datetime_to_iso(end)}
188
+ 'start': {'dateTime': convert_datetime_to_iso(start), 'timeZone': tzlocal.get_localzone_name()},
189
+ 'end': {'dateTime': convert_datetime_to_iso(end), 'timeZone': tzlocal.get_localzone_name()}
189
190
  }
190
-
191
+
191
192
  # Add optional fields
192
193
  if description:
193
194
  event_body['description'] = sanitize_header_value(description)
@@ -197,50 +198,50 @@ def create_event_body(
197
198
  event_body['attendees'] = [attendee.to_dict() for attendee in attendees]
198
199
  if recurrence:
199
200
  event_body['recurrence'] = recurrence
200
-
201
+
201
202
  return event_body
202
203
 
203
204
 
204
205
  def parse_freebusy_response(freebusy_data: Dict[str, Any]) -> FreeBusyResponse:
205
206
  """
206
207
  Parse a freebusy response from Google Calendar API.
207
-
208
+
208
209
  Args:
209
210
  freebusy_data: Dictionary containing freebusy response from API
210
-
211
+
211
212
  Returns:
212
213
  FreeBusyResponse object with parsed data
213
-
214
+
214
215
  Raises:
215
216
  ValueError: If the response data is invalid
216
217
  """
217
218
  if not freebusy_data:
218
219
  raise ValueError("Empty freebusy response data")
219
-
220
+
220
221
  try:
221
222
  # Parse time range
222
223
  time_min = freebusy_data.get("timeMin")
223
224
  time_max = freebusy_data.get("timeMax")
224
-
225
+
225
226
  if not time_min or not time_max:
226
227
  raise ValueError("Missing timeMin or timeMax in freebusy response")
227
-
228
+
228
229
  # Parse start and end times
229
230
  start = datetime.fromisoformat(time_min.replace('Z', '+00:00'))
230
231
  end = datetime.fromisoformat(time_max.replace('Z', '+00:00'))
231
-
232
+
232
233
  # Parse calendar busy periods
233
234
  calendars = {}
234
235
  calendars_data = freebusy_data.get("calendars", {})
235
-
236
+
236
237
  for calendar_id, calendar_data in calendars_data.items():
237
238
  busy_periods = []
238
239
  busy_data = calendar_data.get("busy", [])
239
-
240
+
240
241
  for busy_period in busy_data:
241
242
  period_start_str = busy_period.get("start")
242
243
  period_end_str = busy_period.get("end")
243
-
244
+
244
245
  if period_start_str and period_end_str:
245
246
  try:
246
247
  period_start = datetime.fromisoformat(period_start_str.replace('Z', '+00:00'))
@@ -248,27 +249,27 @@ def parse_freebusy_response(freebusy_data: Dict[str, Any]) -> FreeBusyResponse:
248
249
  busy_periods.append(TimeSlot(start=period_start, end=period_end))
249
250
  except (ValueError, TypeError):
250
251
  continue
251
-
252
+
252
253
  calendars[calendar_id] = busy_periods
253
-
254
+
254
255
  # Parse errors
255
256
  errors = {}
256
257
  errors_data = freebusy_data.get("errors", {})
257
-
258
+
258
259
  for calendar_id, error_data in errors_data.items():
259
260
  if isinstance(error_data, list) and error_data:
260
261
  error_reason = error_data[0].get("reason", "Unknown error")
261
262
  errors[calendar_id] = error_reason
262
263
  elif isinstance(error_data, str):
263
264
  errors[calendar_id] = error_data
264
-
265
+
265
266
  return FreeBusyResponse(
266
267
  start=start,
267
268
  end=end,
268
269
  calendars=calendars,
269
270
  errors=errors
270
271
  )
271
-
272
+
272
273
  except Exception as e:
273
274
  raise ValueError(f"Failed to parse freebusy response: {str(e)}")
274
275
 
@@ -276,23 +277,23 @@ def parse_freebusy_response(freebusy_data: Dict[str, Any]) -> FreeBusyResponse:
276
277
  def merge_overlapping_time_slots(time_slots: List[TimeSlot]) -> List[TimeSlot]:
277
278
  """
278
279
  Merge overlapping time slots into consolidated periods.
279
-
280
+
280
281
  Args:
281
282
  time_slots: List of TimeSlot objects that may overlap
282
-
283
+
283
284
  Returns:
284
285
  List of merged TimeSlot objects with no overlaps
285
286
  """
286
287
  if not time_slots:
287
288
  return []
288
-
289
+
289
290
  # Sort by start time
290
291
  sorted_slots = sorted(time_slots, key=lambda x: x.start)
291
292
  merged = [sorted_slots[0]]
292
-
293
+
293
294
  for current in sorted_slots[1:]:
294
295
  last_merged = merged[-1]
295
-
296
+
296
297
  # Check if current slot overlaps with the last merged slot
297
298
  if current.start <= last_merged.end:
298
299
  # Merge by extending the end time if necessary
@@ -301,38 +302,38 @@ def merge_overlapping_time_slots(time_slots: List[TimeSlot]) -> List[TimeSlot]:
301
302
  else:
302
303
  # No overlap, add as new slot
303
304
  merged.append(current)
304
-
305
+
305
306
  return merged
306
307
 
307
308
 
308
309
  def validate_freebusy_request(
309
- start: datetime,
310
- end: datetime,
311
- calendar_ids: List[str]
310
+ start: datetime,
311
+ end: datetime,
312
+ calendar_ids: List[str]
312
313
  ) -> None:
313
314
  """
314
315
  Validate parameters for a freebusy request.
315
-
316
+
316
317
  Args:
317
318
  start: Start datetime for the query
318
319
  end: End datetime for the query
319
320
  calendar_ids: List of calendar IDs to query
320
-
321
+
321
322
  Raises:
322
323
  ValueError: If any parameter is invalid
323
324
  """
324
325
  from .constants import MAX_FREEBUSY_DAYS_RANGE, MAX_CALENDARS_PER_FREEBUSY_QUERY
325
-
326
+
326
327
  if start >= end:
327
328
  raise ValueError("Start time must be before end time")
328
-
329
+
329
330
  # Check maximum time range (Google's API limit)
330
331
  days_diff = (end - start).days
331
332
  if days_diff > MAX_FREEBUSY_DAYS_RANGE:
332
333
  raise ValueError(f"Time range cannot exceed {MAX_FREEBUSY_DAYS_RANGE} days")
333
-
334
+
334
335
  if not calendar_ids:
335
336
  raise ValueError("At least one calendar ID must be specified")
336
-
337
+
337
338
  if len(calendar_ids) > MAX_CALENDARS_PER_FREEBUSY_QUERY:
338
339
  raise ValueError(f"Cannot query more than {MAX_CALENDARS_PER_FREEBUSY_QUERY} calendars at once")
@@ -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.3"
9
+ version = "1.1.4"
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"}]