google-api-client-wrapper 1.0.0__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.
- google_api_client_wrapper-1.0.0.dist-info/METADATA +103 -0
- google_api_client_wrapper-1.0.0.dist-info/RECORD +39 -0
- google_api_client_wrapper-1.0.0.dist-info/WHEEL +5 -0
- google_api_client_wrapper-1.0.0.dist-info/licenses/LICENSE +21 -0
- google_api_client_wrapper-1.0.0.dist-info/top_level.txt +1 -0
- google_client/__init__.py +6 -0
- google_client/services/__init__.py +13 -0
- google_client/services/calendar/__init__.py +14 -0
- google_client/services/calendar/api_service.py +454 -0
- google_client/services/calendar/constants.py +48 -0
- google_client/services/calendar/exceptions.py +35 -0
- google_client/services/calendar/query_builder.py +314 -0
- google_client/services/calendar/types.py +403 -0
- google_client/services/calendar/utils.py +338 -0
- google_client/services/drive/__init__.py +13 -0
- google_client/services/drive/api_service.py +1133 -0
- google_client/services/drive/constants.py +37 -0
- google_client/services/drive/exceptions.py +60 -0
- google_client/services/drive/query_builder.py +385 -0
- google_client/services/drive/types.py +242 -0
- google_client/services/drive/utils.py +392 -0
- google_client/services/gmail/__init__.py +16 -0
- google_client/services/gmail/api_service.py +715 -0
- google_client/services/gmail/constants.py +6 -0
- google_client/services/gmail/exceptions.py +45 -0
- google_client/services/gmail/query_builder.py +408 -0
- google_client/services/gmail/types.py +285 -0
- google_client/services/gmail/utils.py +426 -0
- google_client/services/tasks/__init__.py +12 -0
- google_client/services/tasks/api_service.py +561 -0
- google_client/services/tasks/constants.py +32 -0
- google_client/services/tasks/exceptions.py +35 -0
- google_client/services/tasks/query_builder.py +324 -0
- google_client/services/tasks/types.py +156 -0
- google_client/services/tasks/utils.py +224 -0
- google_client/user_client.py +208 -0
- google_client/utils/__init__.py +0 -0
- google_client/utils/datetime.py +144 -0
- google_client/utils/validation.py +71 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# API Limits and Defaults
|
|
2
|
+
MAX_RESULTS_LIMIT = 2500
|
|
3
|
+
DEFAULT_MAX_RESULTS = 100
|
|
4
|
+
DEFAULT_DAYS_AHEAD = 7
|
|
5
|
+
|
|
6
|
+
# Field Length Limits
|
|
7
|
+
MAX_SUMMARY_LENGTH = 1024
|
|
8
|
+
MAX_DESCRIPTION_LENGTH = 8192
|
|
9
|
+
MAX_LOCATION_LENGTH = 1024
|
|
10
|
+
MAX_QUERY_LENGTH = 500
|
|
11
|
+
|
|
12
|
+
# Event Status Options
|
|
13
|
+
EVENT_STATUS_CONFIRMED = "confirmed"
|
|
14
|
+
EVENT_STATUS_TENTATIVE = "tentative"
|
|
15
|
+
EVENT_STATUS_CANCELLED = "cancelled"
|
|
16
|
+
|
|
17
|
+
VALID_EVENT_STATUSES = [
|
|
18
|
+
EVENT_STATUS_CONFIRMED,
|
|
19
|
+
EVENT_STATUS_TENTATIVE,
|
|
20
|
+
EVENT_STATUS_CANCELLED
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
# Attendee Response Status Options
|
|
24
|
+
RESPONSE_NEEDS_ACTION = "needsAction"
|
|
25
|
+
RESPONSE_DECLINED = "declined"
|
|
26
|
+
RESPONSE_TENTATIVE = "tentative"
|
|
27
|
+
RESPONSE_ACCEPTED = "accepted"
|
|
28
|
+
|
|
29
|
+
VALID_RESPONSE_STATUSES = [
|
|
30
|
+
RESPONSE_NEEDS_ACTION,
|
|
31
|
+
RESPONSE_DECLINED,
|
|
32
|
+
RESPONSE_TENTATIVE,
|
|
33
|
+
RESPONSE_ACCEPTED
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
# Calendar Default Settings
|
|
37
|
+
DEFAULT_CALENDAR_ID = "primary"
|
|
38
|
+
|
|
39
|
+
# Free/Busy Query Settings
|
|
40
|
+
DEFAULT_FREEBUSY_DURATION_MINUTES = 60
|
|
41
|
+
MAX_FREEBUSY_DAYS_RANGE = 183 # 6 months maximum per Google's API limits
|
|
42
|
+
MAX_CALENDARS_PER_FREEBUSY_QUERY = 50
|
|
43
|
+
DEFAULT_GROUP_EXPANSION_MAX = 25
|
|
44
|
+
DEFAULT_CALENDAR_EXPANSION_MAX = 25
|
|
45
|
+
|
|
46
|
+
# Time Slot Settings
|
|
47
|
+
MIN_TIME_SLOT_DURATION_MINUTES = 15
|
|
48
|
+
MAX_TIME_SLOT_DURATION_MINUTES = 1440 # 24 hours
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
class CalendarError(Exception):
|
|
4
|
+
"""Base exception for Calendar API errors."""
|
|
5
|
+
pass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class EventNotFoundError(CalendarError):
|
|
9
|
+
"""Raised when a calendar event is not found."""
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CalendarNotFoundError(CalendarError):
|
|
14
|
+
"""Raised when a calendar is not found."""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CalendarPermissionError(CalendarError):
|
|
19
|
+
"""Raised when the user lacks permission for a calendar operation."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class EventConflictError(CalendarError):
|
|
24
|
+
"""Raised when there is a conflict with calendar event operations."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class InvalidEventDataError(CalendarError):
|
|
29
|
+
"""Raised when event data is invalid or malformed."""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RecurrenceError(CalendarError):
|
|
34
|
+
"""Raised when there are issues with recurring event operations."""
|
|
35
|
+
pass
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
from datetime import datetime, date, timedelta
|
|
2
|
+
from typing import Optional, List, TYPE_CHECKING
|
|
3
|
+
from ...utils.datetime import date_start, date_end, days_from_today
|
|
4
|
+
from .constants import MAX_RESULTS_LIMIT, MAX_QUERY_LENGTH, DEFAULT_MAX_RESULTS, DEFAULT_CALENDAR_ID
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from .types import CalendarEvent
|
|
8
|
+
from .api_service import CalendarApiService
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EventQueryBuilder:
|
|
12
|
+
"""
|
|
13
|
+
Builder pattern for constructing calendar event queries with a fluent API.
|
|
14
|
+
Provides a clean, readable way to build complex event queries.
|
|
15
|
+
|
|
16
|
+
Example usage:
|
|
17
|
+
events = (CalendarEvent.query()
|
|
18
|
+
.limit(50)
|
|
19
|
+
.in_date_range(start_date, end_date)
|
|
20
|
+
.search("meeting")
|
|
21
|
+
.in_calendar("work@company.com")
|
|
22
|
+
.execute())
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, api_service: "CalendarApiService"):
|
|
26
|
+
self._api_service = api_service
|
|
27
|
+
self._max_results: Optional[int] = DEFAULT_MAX_RESULTS
|
|
28
|
+
self._start: Optional[datetime] = None
|
|
29
|
+
self._end: Optional[datetime] = None
|
|
30
|
+
self._query: Optional[str] = None
|
|
31
|
+
self._calendar_id: str = DEFAULT_CALENDAR_ID
|
|
32
|
+
self._attendee_filter: Optional[str] = None
|
|
33
|
+
self._has_location_filter: Optional[bool] = None
|
|
34
|
+
self._single_events_only: bool = True
|
|
35
|
+
|
|
36
|
+
def limit(self, count: int) -> "EventQueryBuilder":
|
|
37
|
+
"""
|
|
38
|
+
Set the maximum number of events to retrieve.
|
|
39
|
+
Args:
|
|
40
|
+
count: Maximum number of events (1-2500)
|
|
41
|
+
Returns:
|
|
42
|
+
Self for method chaining
|
|
43
|
+
"""
|
|
44
|
+
if count < 1 or count > MAX_RESULTS_LIMIT:
|
|
45
|
+
raise ValueError(f"Limit must be between 1 and {MAX_RESULTS_LIMIT}")
|
|
46
|
+
self._max_results = count
|
|
47
|
+
return self
|
|
48
|
+
|
|
49
|
+
def from_date(self, start: datetime) -> "EventQueryBuilder":
|
|
50
|
+
"""
|
|
51
|
+
Set the start date/time for the query.
|
|
52
|
+
Args:
|
|
53
|
+
start: Start datetime
|
|
54
|
+
Returns:
|
|
55
|
+
Self for method chaining
|
|
56
|
+
"""
|
|
57
|
+
self._start = start
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
def to_date(self, end: datetime) -> "EventQueryBuilder":
|
|
61
|
+
"""
|
|
62
|
+
Set the end date/time for the query.
|
|
63
|
+
Args:
|
|
64
|
+
end: End datetime
|
|
65
|
+
Returns:
|
|
66
|
+
Self for method chaining
|
|
67
|
+
"""
|
|
68
|
+
self._end = end
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def in_date_range(self, start: datetime, end: datetime) -> "EventQueryBuilder":
|
|
72
|
+
"""
|
|
73
|
+
Set both start and end dates for the query.
|
|
74
|
+
Args:
|
|
75
|
+
start: Start datetime
|
|
76
|
+
end: End datetime
|
|
77
|
+
Returns:
|
|
78
|
+
Self for method chaining
|
|
79
|
+
"""
|
|
80
|
+
if start >= end:
|
|
81
|
+
raise ValueError("Start date must be before end date")
|
|
82
|
+
self._start = start
|
|
83
|
+
self._end = end
|
|
84
|
+
return self
|
|
85
|
+
|
|
86
|
+
def search(self, query: str) -> "EventQueryBuilder":
|
|
87
|
+
"""
|
|
88
|
+
Add a text search query to filter events.
|
|
89
|
+
Args:
|
|
90
|
+
query: Search string for event content
|
|
91
|
+
Returns:
|
|
92
|
+
Self for method chaining
|
|
93
|
+
"""
|
|
94
|
+
if len(query) > MAX_QUERY_LENGTH:
|
|
95
|
+
raise ValueError(f"Query string cannot exceed {MAX_QUERY_LENGTH} characters")
|
|
96
|
+
self._query = query
|
|
97
|
+
return self
|
|
98
|
+
|
|
99
|
+
def in_calendar(self, calendar_id: str) -> "EventQueryBuilder":
|
|
100
|
+
"""
|
|
101
|
+
Specify which calendar to query.
|
|
102
|
+
Args:
|
|
103
|
+
calendar_id: Calendar identifier
|
|
104
|
+
Returns:
|
|
105
|
+
Self for method chaining
|
|
106
|
+
"""
|
|
107
|
+
self._calendar_id = calendar_id
|
|
108
|
+
return self
|
|
109
|
+
|
|
110
|
+
def by_attendee(self, email: str) -> "EventQueryBuilder":
|
|
111
|
+
"""
|
|
112
|
+
Filter events by attendee email.
|
|
113
|
+
Args:
|
|
114
|
+
email: Attendee email address
|
|
115
|
+
Returns:
|
|
116
|
+
Self for method chaining
|
|
117
|
+
"""
|
|
118
|
+
self._attendee_filter = email
|
|
119
|
+
return self
|
|
120
|
+
|
|
121
|
+
def with_location(self) -> "EventQueryBuilder":
|
|
122
|
+
"""
|
|
123
|
+
Filter to only events that have a location specified.
|
|
124
|
+
Returns:
|
|
125
|
+
Self for method chaining
|
|
126
|
+
"""
|
|
127
|
+
self._has_location_filter = True
|
|
128
|
+
return self
|
|
129
|
+
|
|
130
|
+
def without_location(self) -> "EventQueryBuilder":
|
|
131
|
+
"""
|
|
132
|
+
Filter to only events that do not have a location specified.
|
|
133
|
+
Returns:
|
|
134
|
+
Self for method chaining
|
|
135
|
+
"""
|
|
136
|
+
self._has_location_filter = False
|
|
137
|
+
return self
|
|
138
|
+
|
|
139
|
+
# Convenience date methods
|
|
140
|
+
def today(self) -> "EventQueryBuilder":
|
|
141
|
+
"""
|
|
142
|
+
Filter to events happening today.
|
|
143
|
+
Returns:
|
|
144
|
+
Self for method chaining
|
|
145
|
+
"""
|
|
146
|
+
today = date.today()
|
|
147
|
+
start_of_day = date_start(today)
|
|
148
|
+
end_of_day = date_end(today)
|
|
149
|
+
return self.in_date_range(start_of_day, end_of_day)
|
|
150
|
+
|
|
151
|
+
def tomorrow(self) -> "EventQueryBuilder":
|
|
152
|
+
"""
|
|
153
|
+
Filter to events happening tomorrow.
|
|
154
|
+
Returns:
|
|
155
|
+
Self for method chaining
|
|
156
|
+
"""
|
|
157
|
+
tomorrow = date.today() + timedelta(days=1)
|
|
158
|
+
start_of_day = date_start(tomorrow)
|
|
159
|
+
end_of_day = date_end(tomorrow)
|
|
160
|
+
return self.in_date_range(start_of_day, end_of_day)
|
|
161
|
+
|
|
162
|
+
def this_week(self) -> "EventQueryBuilder":
|
|
163
|
+
"""
|
|
164
|
+
Filter to events happening this week (Monday to Sunday).
|
|
165
|
+
Returns:
|
|
166
|
+
Self for method chaining
|
|
167
|
+
"""
|
|
168
|
+
today = date.today()
|
|
169
|
+
days_since_monday = today.weekday()
|
|
170
|
+
monday = today - timedelta(days=days_since_monday)
|
|
171
|
+
sunday = monday + timedelta(days=6)
|
|
172
|
+
|
|
173
|
+
start_of_week = date_start(monday)
|
|
174
|
+
end_of_week = date_end(sunday)
|
|
175
|
+
return self.in_date_range(start_of_week, end_of_week)
|
|
176
|
+
|
|
177
|
+
def next_week(self) -> "EventQueryBuilder":
|
|
178
|
+
"""
|
|
179
|
+
Filter to events happening next week (Monday to Sunday).
|
|
180
|
+
Returns:
|
|
181
|
+
Self for method chaining
|
|
182
|
+
"""
|
|
183
|
+
today = date.today()
|
|
184
|
+
days_since_monday = today.weekday()
|
|
185
|
+
next_monday = today + timedelta(days=(7 - days_since_monday))
|
|
186
|
+
next_sunday = next_monday + timedelta(days=6)
|
|
187
|
+
|
|
188
|
+
start_of_week = date_start(next_monday)
|
|
189
|
+
end_of_week = date_end(next_sunday)
|
|
190
|
+
return self.in_date_range(start_of_week, end_of_week)
|
|
191
|
+
|
|
192
|
+
def this_month(self) -> "EventQueryBuilder":
|
|
193
|
+
"""
|
|
194
|
+
Filter to events happening this month.
|
|
195
|
+
Returns:
|
|
196
|
+
Self for method chaining
|
|
197
|
+
"""
|
|
198
|
+
today = date.today()
|
|
199
|
+
first_day = date(today.year, today.month, 1)
|
|
200
|
+
|
|
201
|
+
# Calculate last day of month
|
|
202
|
+
if today.month == 12:
|
|
203
|
+
last_day = date(today.year + 1, 1, 1) - timedelta(days=1)
|
|
204
|
+
else:
|
|
205
|
+
last_day = date(today.year, today.month + 1, 1) - timedelta(days=1)
|
|
206
|
+
|
|
207
|
+
start_of_month = date_start(first_day)
|
|
208
|
+
end_of_month = date_end(last_day)
|
|
209
|
+
return self.in_date_range(start_of_month, end_of_month)
|
|
210
|
+
|
|
211
|
+
def next_days(self, days: int) -> "EventQueryBuilder":
|
|
212
|
+
"""
|
|
213
|
+
Filter to events happening in the next N days.
|
|
214
|
+
Args:
|
|
215
|
+
days: Number of days from today
|
|
216
|
+
Returns:
|
|
217
|
+
Self for method chaining
|
|
218
|
+
"""
|
|
219
|
+
if days < 1:
|
|
220
|
+
raise ValueError("Days must be positive")
|
|
221
|
+
|
|
222
|
+
start = days_from_today(0) # Start of today
|
|
223
|
+
end = days_from_today(days)
|
|
224
|
+
return self.in_date_range(start, end)
|
|
225
|
+
|
|
226
|
+
def last_days(self, days: int) -> "EventQueryBuilder":
|
|
227
|
+
"""
|
|
228
|
+
Filter to events that happened in the last N days.
|
|
229
|
+
Args:
|
|
230
|
+
days: Number of days before today
|
|
231
|
+
Returns:
|
|
232
|
+
Self for method chaining
|
|
233
|
+
"""
|
|
234
|
+
if days < 1:
|
|
235
|
+
raise ValueError("Days must be positive")
|
|
236
|
+
|
|
237
|
+
end = date_end(date.today())
|
|
238
|
+
start = days_from_today(-days)
|
|
239
|
+
return self.in_date_range(start, end)
|
|
240
|
+
|
|
241
|
+
def _apply_post_filters(self, events: List["CalendarEvent"]) -> List["CalendarEvent"]:
|
|
242
|
+
"""
|
|
243
|
+
Apply client-side filters that can't be handled by the API.
|
|
244
|
+
Args:
|
|
245
|
+
events: List of events from API
|
|
246
|
+
Returns:
|
|
247
|
+
Filtered list of events
|
|
248
|
+
"""
|
|
249
|
+
filtered = events
|
|
250
|
+
|
|
251
|
+
# Filter by attendee
|
|
252
|
+
if self._attendee_filter:
|
|
253
|
+
filtered = [event for event in filtered if event.has_attendee(self._attendee_filter)]
|
|
254
|
+
|
|
255
|
+
# Filter by location presence
|
|
256
|
+
if self._has_location_filter is not None:
|
|
257
|
+
if self._has_location_filter:
|
|
258
|
+
filtered = [event for event in filtered if event.location]
|
|
259
|
+
else:
|
|
260
|
+
filtered = [event for event in filtered if not event.location]
|
|
261
|
+
|
|
262
|
+
return filtered
|
|
263
|
+
|
|
264
|
+
def execute(self) -> List["CalendarEvent"]:
|
|
265
|
+
"""
|
|
266
|
+
Execute the query and return the results.
|
|
267
|
+
Returns:
|
|
268
|
+
List of CalendarEvent objects matching the criteria
|
|
269
|
+
Raises:
|
|
270
|
+
ValueError: If query parameters are invalid
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
# Use the service layer implementation
|
|
274
|
+
events = self._api_service.list_events(
|
|
275
|
+
max_results=self._max_results,
|
|
276
|
+
start=self._start,
|
|
277
|
+
end=self._end,
|
|
278
|
+
query=self._query,
|
|
279
|
+
calendar_id=self._calendar_id,
|
|
280
|
+
single_events=self._single_events_only
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# Apply any client-side filters
|
|
284
|
+
filtered_events = self._apply_post_filters(events)
|
|
285
|
+
|
|
286
|
+
return filtered_events
|
|
287
|
+
|
|
288
|
+
def count(self) -> int:
|
|
289
|
+
"""
|
|
290
|
+
Execute the query and return only the count of matching events.
|
|
291
|
+
Returns:
|
|
292
|
+
Number of events matching the criteria
|
|
293
|
+
"""
|
|
294
|
+
return len(self.execute())
|
|
295
|
+
|
|
296
|
+
def first(self) -> Optional["CalendarEvent"]:
|
|
297
|
+
"""
|
|
298
|
+
Execute the query and return only the first matching event.
|
|
299
|
+
Returns:
|
|
300
|
+
First CalendarEvent or None if no matches
|
|
301
|
+
"""
|
|
302
|
+
events = self.limit(1).execute()
|
|
303
|
+
return events[0] if events else None
|
|
304
|
+
|
|
305
|
+
def exists(self) -> bool:
|
|
306
|
+
"""
|
|
307
|
+
Check if any events match the criteria without retrieving them.
|
|
308
|
+
Returns:
|
|
309
|
+
True if at least one event matches, False otherwise
|
|
310
|
+
"""
|
|
311
|
+
return self.limit(1).count() > 0
|
|
312
|
+
|
|
313
|
+
def __repr__(self):
|
|
314
|
+
return f"EventQueryBuilder(query='{self._query}', limit={self._max_results}, calendar_id='{self._calendar_id}')"
|