Skip to content

Commit 735e0d5

Browse files
committed
docs: add detailed docstrings to functions for improved clarity and usage instructions
1 parent 62bae16 commit 735e0d5

File tree

5 files changed

+140
-58
lines changed

5 files changed

+140
-58
lines changed

clean_up_events.py

+49-24
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,56 @@
33

44
# Convert string to datetime
55
def parse_datetime(dt_str):
6+
"""
7+
Convert a string representation of a date and time to a datetime object.
8+
9+
This function uses the `fromisoformat` method to parse the input string,
10+
which should be in ISO format (YYYY-MM-DD[*HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]])
11+
12+
Args:
13+
dt_str (str): A string representing a date and time in ISO format.
14+
15+
Returns:
16+
datetime: A datetime object representing the parsed date and time.
17+
18+
Raises:
19+
ValueError: If the input string is not in a valid ISO format.
20+
"""
621
return datetime.fromisoformat(dt_str)
722

8-
# Calculate free times
923
def calculate_free_times(lessons):
10-
free_times = []
11-
lessons = sorted(lessons, key=lambda x: parse_datetime(x['start']))
12-
13-
# Define the start and end of the day
14-
day_start = parse_datetime(lessons[0]['start']).replace(hour=0, minute=0, second=0, microsecond=0)
15-
day_end = day_start + timedelta(days=1)
16-
17-
# Check for free time before the first lesson
18-
if parse_datetime(lessons[0]['start']) > day_start:
19-
free_times.append({'start': day_start.isoformat(), 'end': lessons[0]['start']})
20-
21-
# Check for free times between lessons
22-
for i in range(len(lessons) - 1):
23-
end_current = parse_datetime(lessons[i]['end'])
24-
start_next = parse_datetime(lessons[i + 1]['start'])
25-
if end_current < start_next:
26-
free_times.append({'start': end_current.isoformat(), 'end': start_next.isoformat()})
27-
28-
# Check for free time after the last lesson
29-
if parse_datetime(lessons[-1]['end']) < day_end:
30-
free_times.append({'start': lessons[-1]['end'], 'end': day_end.isoformat()})
31-
32-
return free_times[1:]
24+
"""
25+
Calculate the free time slots from a list of scheduled lessons.
26+
27+
This function takes a list of lessons, where each lesson is represented as a dictionary with 'start' and 'end' keys.
28+
It then calculates the free time slots by compressing the scheduled lessons and merging overlapping time slots.
29+
30+
Args:
31+
lessons (list): A list of dictionaries, where each dictionary represents a lesson with 'start' and 'end' keys.
32+
33+
Returns:
34+
list: A list of dictionaries representing the free time slots. Each dictionary contains 'start' and 'end' keys.
35+
36+
Example:
37+
lessons = [
38+
{'start': '2022-01-01 09:00', 'end': '2022-01-01 11:00'},
39+
{'start': '2022-01-01 10:00', 'end': '2022-01-01 12:00'},
40+
{'start': '2022-01-01 13:00', 'end': '2022-01-01 14:00'}
41+
]
42+
free_times = calculate_free_times(lessons)
43+
print(free_times)
44+
45+
## Output:
46+
[{'start': '2022-01-01 09:00', 'end': '2022-01-01 10:00'}, {'start': '2022-01-01 12:00', 'end': '2022-01-01 13:00'}]
47+
"""
48+
cleaned_data = [{'start': entry['start'], 'end': entry['end']} for entry in lessons]
49+
sorted_data = sorted(cleaned_data, key=lambda x: x['start'])
50+
compressed_data = []
51+
for entry in sorted_data:
52+
if compressed_data and compressed_data[-1]['end'] == entry['start']:
53+
compressed_data[-1]['end'] = entry['end']
54+
else:
55+
compressed_data.append(entry)
56+
57+
return compressed_data
3358

get_data.py

+21-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,29 @@
55
load_dotenv(override=True)
66

77
def get_data(from_date, to_date, session_id, auth_token, user_id, school_id):
8+
"""
9+
Retrieves schedule data from the ALL4SCHOOLS API.
10+
11+
This function sends a POST request to the ALL4SCHOOLS API to fetch schedule data
12+
for a specific student within a given date range.
13+
14+
Parameters:
15+
from_date (str): The start date of the schedule period (format: 'YYYY-MM-DD').
16+
to_date (str): The end date of the schedule period (format: 'YYYY-MM-DD').
17+
session_id (str): The ASP.NET session ID for authentication.
18+
auth_token (str): The ASPXAUTH token for authentication.
19+
user_id (str): The ID of the student whose schedule is being requested.
20+
school_id (str): The ID of the school.
21+
22+
Returns:
23+
dict or None: A dictionary containing the schedule data if the request is successful
24+
and the response is in JSON format. Returns None if the request fails
25+
or the response is not in JSON format.
26+
"""
827
base_url = os.getenv('ALL4SCHOOLS_URL')
928
api_endpoint = 'api/api/Schedule/GetSchedule'
1029
full_url = f"{base_url}/{api_endpoint}"
11-
30+
1231
data = {
1332
"schoolId": school_id,
1433
"studentId": user_id,
@@ -34,4 +53,4 @@ def get_data(from_date, to_date, session_id, auth_token, user_id, school_id):
3453
return None
3554
else:
3655
print(f"Request failed with status code {response.status_code}")
37-
return None
56+
return None

get_school_id.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@
55
load_dotenv(override=True)
66

77
def get_school_id(session_id, auth_token):
8+
"""
9+
Retrieves the school ID for the current user from the ALL4SCHOOLS API.
10+
11+
This function makes a GET request to the ALL4SCHOOLS API to fetch the schools and settings
12+
for the current user. It then extracts and returns the ID of the first school in the response.
13+
14+
Args:
15+
session_id (str): The ASP.NET session ID for authentication.
16+
auth_token (str): The ASPXAUTH token for authentication.
17+
18+
Returns:
19+
str: The ID of the first school associated with the current user.
20+
21+
Raises:
22+
requests.exceptions.HTTPError: If the API request fails or returns a non-200 status code.
23+
"""
824
base_url = os.getenv('ALL4SCHOOLS_URL')
925
api_endpoint = 'api/Api/AppUser/GetSchoolsAndSettingsForCurrentUser'
1026
full_url = f"{base_url}/{api_endpoint}"
@@ -16,8 +32,8 @@ def get_school_id(session_id, auth_token):
1632

1733
# Assuming you will use requests to make the API call
1834
response = requests.get(full_url, cookies=cookies)
19-
35+
2036
if response.status_code == 200:
2137
return response.json()[0]["id"]
2238
else:
23-
response.raise_for_status()
39+
response.raise_for_status()

main.py

+34-9
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@ def print_progress_bar(processed, total):
124124

125125

126126
def process_event_with_progress(event, lock, processed_events, total_events, last_print_time):
127+
"""
128+
Processes an event and updates the progress bar.
129+
130+
Args:
131+
event: The event to be processed.
132+
lock: A threading lock to ensure thread-safe updates to the processed_events counter.
133+
processed_events (list): A list containing a single integer representing the number of processed events.
134+
total_events (int): The total number of events to be processed.
135+
last_print_time (list): A list containing a single float representing the last time the progress bar was printed.
136+
137+
Returns:
138+
None
139+
"""
127140
process_event(event)
128141
with lock:
129142
processed_events[0] += 1
@@ -132,7 +145,21 @@ def process_event_with_progress(event, lock, processed_events, total_events, las
132145
print_progress_bar(processed_events[0], total_events)
133146
last_print_time[0] = current_time
134147

148+
135149
def process_free_time_range(time_range, lock, processed_free_times, total_free_times, last_print_time_free):
150+
"""
151+
Processes a given time range by removing events within that range and updating the progress.
152+
153+
Args:
154+
time_range (tuple): The time range to process.
155+
lock (threading.Lock): A lock to ensure thread-safe operations.
156+
processed_free_times (list): A list containing the count of processed free times.
157+
total_free_times (int): The total number of free times to process.
158+
last_print_time_free (list): A list containing the last time the progress was printed.
159+
160+
Returns:
161+
None
162+
"""
136163
remove_events_in_time_range(time_range)
137164
with lock:
138165
processed_free_times[0] += 1
@@ -169,8 +196,12 @@ def main():
169196

170197
print_with_timestamp("\033[94mCompressing events...\033[0m")
171198
compressed_data = compress_events(parsed_data)
199+
import json
200+
with open("compressed.json", 'w') as json_file:
201+
json.dump(compressed_data, json_file, indent=4)
172202
print_with_timestamp("\033[92mCompressed events\033[0m")
173203

204+
174205
print_with_timestamp("\033[94mAdding events to calendar...\033[0m")
175206
total_events = len(compressed_data)
176207
processed_events = [0]
@@ -187,15 +218,9 @@ def main():
187218
free_times = calculate_free_times(compressed_data)
188219
print_with_timestamp("\033[92mCalculated free times\033[0m")
189220

190-
print_with_timestamp("\033[94mRemoving free times from calendar...\033[0m")
191-
total_free_times = len(free_times)
192-
processed_free_times = [0]
193-
last_print_time_free = [time.time()]
194-
195-
with ThreadPoolExecutor() as executor:
196-
executor.map(lambda time_range: process_free_time_range(time_range, lock, processed_free_times, total_free_times, last_print_time_free), free_times)
197-
print_progress_bar(total_free_times, total_free_times)
198-
print_with_timestamp("\033[92mAll free times removed.\033[0m")
221+
print_with_timestamp("\033[94mRemoving events from calendar...\033[0m")
222+
remove_events_in_time_range(free_times)
223+
print_with_timestamp("\033[92mRemoved events from calendar\033[0m")
199224

200225
if __name__ == "__main__":
201226
interval_minutes = int(os.getenv('INTERVAL_MINUTES', 10))

set_free_time_in_calander.py

+18-21
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,21 @@
1111

1212
def remove_events_in_time_range(time_range):
1313
"""
14-
Remove events from a SOGo calendar that fall within a specified time range.
14+
Remove events from a calendar that do not overlap with specified free time slots.
1515
16-
This function connects to a SOGo calendar, retrieves events within a certain date range,
17-
and deletes any events that overlap with the specified time range.
16+
This function connects to a calendar service, retrieves events within a specified date range,
17+
and deletes events that do not overlap with the provided free time slots.
1818
1919
Parameters:
20-
time_range (dict): A dictionary containing the start and end times for the range
21-
in which events should be removed. It should have two keys:
22-
'start' and 'end', with string values that can be parsed into
23-
datetime objects.
20+
time_range (list of dict): A list of dictionaries, where each dictionary represents a free time slot
21+
with 'start' and 'end' keys containing datetime strings.
2422
2523
Returns:
2624
None
2725
2826
Note:
29-
- This function relies on environment variables for calendar connection details.
30-
- It uses the caldav library to interact with the calendar.
31-
- Events are deleted if they start, end, or span within the specified time range.
27+
- The function uses environment variables for calendar service credentials and configuration.
28+
- Events are deleted if they fall completely outside the specified free time slots.
3229
"""
3330
url = os.getenv('SOGO_CALENDAR_URL')
3431
username = os.getenv('SOGO_USERNAME')
@@ -39,17 +36,17 @@ def remove_events_in_time_range(time_range):
3936

4037
start = datetime.now()
4138
end = datetime.now() + timedelta(days=int(os.getenv('DAYS_TO_UPDATE')))
42-
existing_events = calendar__.date_search(start, end)
39+
events_to_delete = calendar__.date_search(start, end)
4340

44-
range_start = parse(time_range['start'])
45-
range_end = parse(time_range['end'])
4641

47-
for event in existing_events:
48-
event_start = event.vobject_instance.vevent.dtstart.value
49-
event_end = event.vobject_instance.vevent.dtend.value
50-
51-
if (event_start > range_start and event_start < range_end) or \
52-
(event_end > range_start and event_end < range_end) or \
53-
(event_start < range_start and event_end > range_end):
54-
event.delete()
42+
for free_slot in time_range:
43+
free_start = parse(free_slot['start'])
44+
free_end = parse(free_slot['end'])
45+
for event in events_to_delete:
46+
event_start = event.vobject_instance.vevent.dtstart.value
47+
event_end = event.vobject_instance.vevent.dtend.value
48+
if (free_start <= event_start < free_end) or (free_start < event_end <= free_end) or (event_start <= free_start and event_end >= free_end):
49+
events_to_delete.remove(event)
5550

51+
for event in events_to_delete:
52+
event.delete()

0 commit comments

Comments
 (0)