Skip to content

Commit 3a8f523

Browse files
Merge pull request #2 from Programming-The-Next-Step-2026/week-2
Week 2
2 parents d3a3d68 + 96f6bd0 commit 3a8f523

11 files changed

Lines changed: 907 additions & 21 deletions

File tree

.coverage

52 KB
Binary file not shown.

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
*.egg-info/
22
__pycache__/
33
*.pyc
4-
.DS_Store
4+
.DS_Store
5+
credentials.json
6+
token.json

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2025 Magdalena Grubesa
1+
Copyright (c) 2026 Magdalena Grubesa
22

33
Permission is hereby granted, free of charge, to any person obtaining a copy
44
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,61 @@
33
A smart study planner that builds your revision schedule automatically based on your exam dates and availability.
44

55
## Features
6-
- Input your exams with dates, difficulty and estimated study hours
7-
- Set daily available study hours
8-
- Get an automatically generated revision schedule
9-
- Receive warnings when schedule is overloaded
10-
- Receive study tips based on the schedule
6+
- Input your exams with dates and estimated study hours
7+
- Set daily available study hours and commitments
8+
- Import commitments automatically from Google Calendar
9+
- Edit/ commitments inside the planner
10+
- Get an automatically generated color coded revision schedule
11+
- Receive warnings when there is not enough time for an exam
12+
- Get personalised study tips based on psychology research
13+
- Optional spaced repetition schedule based on the Ebbinghaus forgetting curve
14+
15+
## Installation
16+
```bash
17+
pip install -e .
18+
```
19+
20+
## Usage
21+
```bash
22+
python src/study_smart/app.py
23+
```
24+
25+
## Documentation
26+
27+
To view the documentation for schedule.py, run:
28+
29+
```bash
30+
pdoc --docformat google src/study_smart/schedule.py
31+
```
32+
33+
To view the documentation for google_calendar.py, run:
34+
35+
```bash
36+
pdoc --docformat google src/study_smart/google_calendar.py
37+
```
38+
39+
## Testing
40+
41+
To run the unit tests:
42+
43+
```bash
44+
pytest tests/
45+
```
46+
47+
To run tests with coverage report:
48+
49+
```bash
50+
pytest --cov=study_smart tests/
51+
```
52+
53+
## Google Calendar Integration
54+
To use the Google Calendar import feature you need:
55+
1. A Google account
56+
2. A `credentials.json` file — get this from [Google Cloud Console](https://console.cloud.google.com) by creating a project, enabling the Google Calendar API, and creating OAuth 2.0 credentials (Desktop app)
57+
3. Place `credentials.json` in the root of the project folder
58+
4. On first run a browser window will open asking you to grant calendar access — after that a `token.json` file is saved automatically
59+
60+
Note: `credentials.json` and `token.json` are excluded from version control for security reasons.
61+
62+
## Study Tips
63+
Hard-coded tips, based on psychological research.

pyproject.toml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@ classifiers = [
1818
license = "MIT"
1919
license-files = ["LICEN[CS]E*"]
2020
dependencies = [
21-
"shiny",
2221
"pandas",
23-
"plotnine",
24-
"openpyxl",
22+
"google-auth",
23+
"google-auth-oauthlib",
24+
"google-api-python-client",
25+
]
26+
[project.optional-dependencies]
27+
dev = [
28+
"pytest",
29+
"pytest-cov",
2530
]
26-
2731
[project.urls]
2832
Homepage = "https://github.com/Programming-The-Next-Step-2026/StudySmart"
2933
Issues = "https://github.com/Programming-The-Next-Step-2026/StudySmart/issues"

src/study_smart/__init__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
from .schedule import (
2-
available_hours_per_day
3-
)
2+
build_schedule,
3+
update_schedule,
4+
generate_tips
5+
)
6+
from .google_calendar import import_commitments_from_google_calendar

src/study_smart/google_calendar.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import os
2+
from datetime import datetime
3+
from google.auth.transport.requests import Request
4+
from google.oauth2.credentials import Credentials
5+
from google_auth_oauthlib.flow import InstalledAppFlow
6+
from googleapiclient.discovery import build
7+
8+
SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"]
9+
10+
11+
def _get_calendar_service():
12+
"""Internal — authenticates and returns Google Calendar service."""
13+
creds = None
14+
15+
if os.path.exists("token.json"):
16+
creds = Credentials.from_authorized_user_file("token.json", SCOPES)
17+
18+
if not creds or not creds.valid:
19+
if creds and creds.expired and creds.refresh_token:
20+
creds.refresh(Request())
21+
else:
22+
flow = InstalledAppFlow.from_client_secrets_file(
23+
"credentials.json", SCOPES
24+
)
25+
creds = flow.run_local_server(port=0)
26+
27+
with open("token.json", "w") as token:
28+
token.write(creds.to_json())
29+
30+
return build("calendar", "v3", credentials=creds)
31+
32+
def import_commitments_from_google_calendar(start_date, end_date):
33+
"""
34+
Imports calendar events as commitments from Google Calendar.
35+
36+
Fetches all events between start_date and end_date and converts
37+
them into a commitments dictionary mapping dates to blocked hours.
38+
39+
Note: Requires a live Google Calendar connection and valid credentials.
40+
Authentication is handled via OAuth 2.0. On first run, a browser window
41+
will open asking the user to grant calendar access.
42+
43+
Args:
44+
start_date (date): First day to fetch events from.
45+
end_date (date): Last day to fetch events until.
46+
47+
Returns:
48+
tuple: A tuple containing two dictionaries:
49+
- commitments (dict): Mapping dates to total blocked hours that day.
50+
- event_name (dict): Mapping dates to lists of event titles.
51+
52+
Example:
53+
>>> from datetime import date, datetime
54+
>>> commitments, event_name = import_commitments_from_google_calendar(
55+
... start_date=date(2026, 5, 13),
56+
... end_date=date(2026, 6, 5)
57+
... )
58+
"""
59+
service = _get_calendar_service()
60+
61+
start = datetime.combine(start_date, datetime.min.time()).isoformat() + "Z"
62+
end = datetime.combine(end_date, datetime.min.time()).isoformat() + "Z"
63+
64+
events_result = service.events().list(
65+
calendarId="primary",
66+
timeMin=start,
67+
timeMax=end,
68+
singleEvents=True,
69+
orderBy="startTime"
70+
).execute()
71+
72+
events = events_result.get("items", [])
73+
74+
commitments = {}
75+
event_name = {}
76+
for event in events:
77+
if "dateTime" not in event["start"]:
78+
continue
79+
80+
start_time = datetime.fromisoformat(
81+
event["start"]["dateTime"].replace("Z", "+00:00")
82+
)
83+
end_time = datetime.fromisoformat(
84+
event["end"]["dateTime"].replace("Z", "+00:00")
85+
)
86+
duration_hours = (end_time - start_time).seconds / 3600
87+
event_date = start_time.date()
88+
event_title = event.get("summary", "Unnamed event")
89+
90+
#to prevent overwriting existing commitments, sum durations for events on the same day
91+
if event_date in commitments:
92+
commitments[event_date] += duration_hours
93+
event_name[event_date].append(event_title)
94+
else:
95+
commitments[event_date] = duration_hours
96+
event_name[event_date] = [event_title]
97+
98+
return (commitments, event_name)
99+
100+

0 commit comments

Comments
 (0)