timeback-edubridge 0.1.0__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 (34) hide show
  1. timeback_edubridge-0.1.0/.gitignore +48 -0
  2. timeback_edubridge-0.1.0/PKG-INFO +230 -0
  3. timeback_edubridge-0.1.0/README.md +209 -0
  4. timeback_edubridge-0.1.0/pyproject.toml +34 -0
  5. timeback_edubridge-0.1.0/pytest.ini +5 -0
  6. timeback_edubridge-0.1.0/src/timeback_edubridge/__init__.py +158 -0
  7. timeback_edubridge-0.1.0/src/timeback_edubridge/client.py +230 -0
  8. timeback_edubridge-0.1.0/src/timeback_edubridge/constants.py +24 -0
  9. timeback_edubridge-0.1.0/src/timeback_edubridge/exceptions.py +30 -0
  10. timeback_edubridge-0.1.0/src/timeback_edubridge/lib/__init__.py +10 -0
  11. timeback_edubridge-0.1.0/src/timeback_edubridge/lib/pagination.py +281 -0
  12. timeback_edubridge-0.1.0/src/timeback_edubridge/lib/transport.py +110 -0
  13. timeback_edubridge-0.1.0/src/timeback_edubridge/py.typed +0 -0
  14. timeback_edubridge-0.1.0/src/timeback_edubridge/resources/__init__.py +19 -0
  15. timeback_edubridge-0.1.0/src/timeback_edubridge/resources/analytics.py +251 -0
  16. timeback_edubridge-0.1.0/src/timeback_edubridge/resources/applications.py +73 -0
  17. timeback_edubridge-0.1.0/src/timeback_edubridge/resources/enrollments.py +213 -0
  18. timeback_edubridge-0.1.0/src/timeback_edubridge/resources/learning_reports.py +80 -0
  19. timeback_edubridge-0.1.0/src/timeback_edubridge/resources/subject_track.py +135 -0
  20. timeback_edubridge-0.1.0/src/timeback_edubridge/resources/users.py +213 -0
  21. timeback_edubridge-0.1.0/src/timeback_edubridge/types/__init__.py +93 -0
  22. timeback_edubridge-0.1.0/src/timeback_edubridge/types/analytics.py +153 -0
  23. timeback_edubridge-0.1.0/src/timeback_edubridge/types/applications.py +43 -0
  24. timeback_edubridge-0.1.0/src/timeback_edubridge/types/base.py +51 -0
  25. timeback_edubridge-0.1.0/src/timeback_edubridge/types/enrollments.py +139 -0
  26. timeback_edubridge-0.1.0/src/timeback_edubridge/types/learning_reports.py +43 -0
  27. timeback_edubridge-0.1.0/src/timeback_edubridge/types/subject_track.py +67 -0
  28. timeback_edubridge-0.1.0/src/timeback_edubridge/types/users.py +102 -0
  29. timeback_edubridge-0.1.0/src/timeback_edubridge/utils.py +200 -0
  30. timeback_edubridge-0.1.0/tests/__init__.py +1 -0
  31. timeback_edubridge-0.1.0/tests/test_client.py +132 -0
  32. timeback_edubridge-0.1.0/tests/test_types.py +197 -0
  33. timeback_edubridge-0.1.0/tests/test_utils.py +240 -0
  34. timeback_edubridge-0.1.0/tests/test_validation.py +202 -0
@@ -0,0 +1,48 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib64/
14
+ parts/
15
+ sdist/
16
+ var/
17
+ wheels/
18
+ *.egg-info/
19
+ .installed.cfg
20
+ *.egg
21
+
22
+ # Virtual environments
23
+ .venv/
24
+ venv/
25
+ ENV/
26
+
27
+ # uv
28
+ uv.lock
29
+
30
+ # ruff
31
+ .ruff_cache/
32
+
33
+ # Testing
34
+ .pytest_cache/
35
+ .coverage
36
+ htmlcov/
37
+ .tox/
38
+ .nox/
39
+
40
+ # IDEs
41
+ .idea/
42
+ .vscode/
43
+ *.swp
44
+ *.swo
45
+
46
+ # OS
47
+ .DS_Store
48
+ Thumbs.db
@@ -0,0 +1,230 @@
1
+ Metadata-Version: 2.4
2
+ Name: timeback-edubridge
3
+ Version: 0.1.0
4
+ Summary: Python client for the Timeback EduBridge API
5
+ Project-URL: Homepage, https://timeback.ai
6
+ Project-URL: Documentation, https://docs.timeback.ai
7
+ Project-URL: Repository, https://github.com/timeback-ai/timeback-python
8
+ Author: Timeback
9
+ License-Expression: MIT
10
+ Keywords: api,edubridge,education,sdk,timeback
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: timeback-common>=0.1.0
20
+ Description-Content-Type: text/markdown
21
+
22
+ # Timeback EduBridge Client
23
+
24
+ Python client for the Timeback EduBridge API with async support.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ pip install timeback-edubridge
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ```python
35
+ from timeback_edubridge import EdubridgeClient
36
+
37
+ # Initialize with explicit configuration
38
+ client = EdubridgeClient(
39
+ base_url="https://api.timeback.ai",
40
+ auth_url="https://auth.timeback.ai/oauth2/token",
41
+ client_id="your-client-id",
42
+ client_secret="your-client-secret",
43
+ )
44
+
45
+ # Or use environment variables with a prefix
46
+ client = EdubridgeClient(env="PRODUCTION")
47
+ # Reads: PRODUCTION_EDUBRIDGE_BASE_URL, PRODUCTION_EDUBRIDGE_TOKEN_URL, etc.
48
+ ```
49
+
50
+ ## Resources
51
+
52
+ ### Enrollments
53
+
54
+ ```python
55
+ # List enrollments for a user
56
+ enrollments = await client.enrollments.list(user_id="user-123")
57
+
58
+ # Enroll a user in a course
59
+ enrollment = await client.enrollments.enroll(
60
+ user_id="user-123",
61
+ course_id="course-456",
62
+ school_id="school-789", # Optional
63
+ )
64
+
65
+ # Unenroll a user
66
+ await client.enrollments.unenroll(
67
+ user_id="user-123",
68
+ course_id="course-456",
69
+ )
70
+
71
+ # Reset goals for a course
72
+ result = await client.enrollments.reset_goals("course-456")
73
+
74
+ # Reset a user's progress
75
+ await client.enrollments.reset_progress("user-123", "course-456")
76
+
77
+ # Get default class for a course
78
+ default_class = await client.enrollments.get_default_class("course-456")
79
+ ```
80
+
81
+ ### Users
82
+
83
+ ```python
84
+ # List users by role
85
+ users = await client.users.list(roles=["student", "teacher"])
86
+
87
+ # Convenience methods
88
+ students = await client.users.list_students()
89
+ teachers = await client.users.list_teachers()
90
+
91
+ # Search users
92
+ results = await client.users.search(
93
+ roles=["student"],
94
+ search="john",
95
+ limit=50,
96
+ )
97
+
98
+ # With additional filters
99
+ filtered = await client.users.list(
100
+ roles=["student"],
101
+ org_sourced_ids=["school-123"],
102
+ limit=100,
103
+ offset=0,
104
+ )
105
+ ```
106
+
107
+ ### Analytics
108
+
109
+ ```python
110
+ # Get activity for a date range
111
+ activity = await client.analytics.get_activity(
112
+ student_id="student-123", # or email="student@example.com"
113
+ start_date="2025-01-01",
114
+ end_date="2025-01-31",
115
+ timezone="America/New_York",
116
+ )
117
+
118
+ # Get weekly facts
119
+ facts = await client.analytics.get_weekly_facts(
120
+ student_id="student-123",
121
+ week_date="2025-01-15",
122
+ )
123
+
124
+ # Get enrollment-specific facts
125
+ enrollment_facts = await client.analytics.get_enrollment_facts(
126
+ enrollment_id="enrollment-123",
127
+ start_date="2025-01-01",
128
+ end_date="2025-01-31",
129
+ )
130
+
131
+ # Get highest grade mastered
132
+ grade = await client.analytics.get_highest_grade_mastered(
133
+ student_id="student-123",
134
+ subject="Math",
135
+ )
136
+ ```
137
+
138
+ ### Applications
139
+
140
+ ```python
141
+ # List all applications
142
+ apps = await client.applications.list()
143
+
144
+ # Get metrics for an application
145
+ metrics = await client.applications.get_metrics("app-123")
146
+ ```
147
+
148
+ ### Subject Tracks
149
+
150
+ ```python
151
+ from timeback_edubridge import SubjectTrackInput
152
+
153
+ # List all subject tracks
154
+ tracks = await client.subject_tracks.list()
155
+
156
+ # Create or update a subject track
157
+ track = await client.subject_tracks.upsert(
158
+ id="track-123",
159
+ data=SubjectTrackInput(
160
+ subject="Math",
161
+ grade_level="9",
162
+ target_course_id="course-456",
163
+ ),
164
+ )
165
+
166
+ # Delete a subject track
167
+ await client.subject_tracks.delete("track-123")
168
+
169
+ # List subject track groups
170
+ groups = await client.subject_tracks.list_groups()
171
+ ```
172
+
173
+ ### Learning Reports
174
+
175
+ ```python
176
+ # Get MAP profile for a user
177
+ profile = await client.learning_reports.get_map_profile("user-123")
178
+
179
+ # Get time saved metrics
180
+ time_saved = await client.learning_reports.get_time_saved("user-123")
181
+ ```
182
+
183
+ ## Context Manager
184
+
185
+ The client can be used as an async context manager:
186
+
187
+ ```python
188
+ async with EdubridgeClient(base_url="...") as client:
189
+ enrollments = await client.enrollments.list(user_id="user-123")
190
+ # Client is automatically closed
191
+ ```
192
+
193
+ ## Error Handling
194
+
195
+ ```python
196
+ from timeback_edubridge import (
197
+ EdubridgeError,
198
+ AuthenticationError,
199
+ ForbiddenError,
200
+ NotFoundError,
201
+ ValidationError,
202
+ APIError,
203
+ )
204
+
205
+ try:
206
+ enrollments = await client.enrollments.list(user_id="user-123")
207
+ except AuthenticationError:
208
+ print("Invalid credentials")
209
+ except ForbiddenError:
210
+ print("Access denied")
211
+ except NotFoundError:
212
+ print("Resource not found")
213
+ except ValidationError as e:
214
+ print(f"Invalid request: {e}")
215
+ except APIError as e:
216
+ print(f"API error {e.status_code}: {e}")
217
+ ```
218
+
219
+ ## Environment Variables
220
+
221
+ When using `env` parameter, the client looks for these variables:
222
+
223
+ | Variable | Description |
224
+ |----------|-------------|
225
+ | `{PREFIX}_EDUBRIDGE_BASE_URL` | Base URL for the API |
226
+ | `{PREFIX}_EDUBRIDGE_TOKEN_URL` | OAuth2 token endpoint |
227
+ | `{PREFIX}_EDUBRIDGE_CLIENT_ID` | OAuth2 client ID |
228
+ | `{PREFIX}_EDUBRIDGE_CLIENT_SECRET` | OAuth2 client secret |
229
+
230
+ Without a prefix, it uses the variables without the prefix (e.g., `EDUBRIDGE_BASE_URL`).
@@ -0,0 +1,209 @@
1
+ # Timeback EduBridge Client
2
+
3
+ Python client for the Timeback EduBridge API with async support.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install timeback-edubridge
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ from timeback_edubridge import EdubridgeClient
15
+
16
+ # Initialize with explicit configuration
17
+ client = EdubridgeClient(
18
+ base_url="https://api.timeback.ai",
19
+ auth_url="https://auth.timeback.ai/oauth2/token",
20
+ client_id="your-client-id",
21
+ client_secret="your-client-secret",
22
+ )
23
+
24
+ # Or use environment variables with a prefix
25
+ client = EdubridgeClient(env="PRODUCTION")
26
+ # Reads: PRODUCTION_EDUBRIDGE_BASE_URL, PRODUCTION_EDUBRIDGE_TOKEN_URL, etc.
27
+ ```
28
+
29
+ ## Resources
30
+
31
+ ### Enrollments
32
+
33
+ ```python
34
+ # List enrollments for a user
35
+ enrollments = await client.enrollments.list(user_id="user-123")
36
+
37
+ # Enroll a user in a course
38
+ enrollment = await client.enrollments.enroll(
39
+ user_id="user-123",
40
+ course_id="course-456",
41
+ school_id="school-789", # Optional
42
+ )
43
+
44
+ # Unenroll a user
45
+ await client.enrollments.unenroll(
46
+ user_id="user-123",
47
+ course_id="course-456",
48
+ )
49
+
50
+ # Reset goals for a course
51
+ result = await client.enrollments.reset_goals("course-456")
52
+
53
+ # Reset a user's progress
54
+ await client.enrollments.reset_progress("user-123", "course-456")
55
+
56
+ # Get default class for a course
57
+ default_class = await client.enrollments.get_default_class("course-456")
58
+ ```
59
+
60
+ ### Users
61
+
62
+ ```python
63
+ # List users by role
64
+ users = await client.users.list(roles=["student", "teacher"])
65
+
66
+ # Convenience methods
67
+ students = await client.users.list_students()
68
+ teachers = await client.users.list_teachers()
69
+
70
+ # Search users
71
+ results = await client.users.search(
72
+ roles=["student"],
73
+ search="john",
74
+ limit=50,
75
+ )
76
+
77
+ # With additional filters
78
+ filtered = await client.users.list(
79
+ roles=["student"],
80
+ org_sourced_ids=["school-123"],
81
+ limit=100,
82
+ offset=0,
83
+ )
84
+ ```
85
+
86
+ ### Analytics
87
+
88
+ ```python
89
+ # Get activity for a date range
90
+ activity = await client.analytics.get_activity(
91
+ student_id="student-123", # or email="student@example.com"
92
+ start_date="2025-01-01",
93
+ end_date="2025-01-31",
94
+ timezone="America/New_York",
95
+ )
96
+
97
+ # Get weekly facts
98
+ facts = await client.analytics.get_weekly_facts(
99
+ student_id="student-123",
100
+ week_date="2025-01-15",
101
+ )
102
+
103
+ # Get enrollment-specific facts
104
+ enrollment_facts = await client.analytics.get_enrollment_facts(
105
+ enrollment_id="enrollment-123",
106
+ start_date="2025-01-01",
107
+ end_date="2025-01-31",
108
+ )
109
+
110
+ # Get highest grade mastered
111
+ grade = await client.analytics.get_highest_grade_mastered(
112
+ student_id="student-123",
113
+ subject="Math",
114
+ )
115
+ ```
116
+
117
+ ### Applications
118
+
119
+ ```python
120
+ # List all applications
121
+ apps = await client.applications.list()
122
+
123
+ # Get metrics for an application
124
+ metrics = await client.applications.get_metrics("app-123")
125
+ ```
126
+
127
+ ### Subject Tracks
128
+
129
+ ```python
130
+ from timeback_edubridge import SubjectTrackInput
131
+
132
+ # List all subject tracks
133
+ tracks = await client.subject_tracks.list()
134
+
135
+ # Create or update a subject track
136
+ track = await client.subject_tracks.upsert(
137
+ id="track-123",
138
+ data=SubjectTrackInput(
139
+ subject="Math",
140
+ grade_level="9",
141
+ target_course_id="course-456",
142
+ ),
143
+ )
144
+
145
+ # Delete a subject track
146
+ await client.subject_tracks.delete("track-123")
147
+
148
+ # List subject track groups
149
+ groups = await client.subject_tracks.list_groups()
150
+ ```
151
+
152
+ ### Learning Reports
153
+
154
+ ```python
155
+ # Get MAP profile for a user
156
+ profile = await client.learning_reports.get_map_profile("user-123")
157
+
158
+ # Get time saved metrics
159
+ time_saved = await client.learning_reports.get_time_saved("user-123")
160
+ ```
161
+
162
+ ## Context Manager
163
+
164
+ The client can be used as an async context manager:
165
+
166
+ ```python
167
+ async with EdubridgeClient(base_url="...") as client:
168
+ enrollments = await client.enrollments.list(user_id="user-123")
169
+ # Client is automatically closed
170
+ ```
171
+
172
+ ## Error Handling
173
+
174
+ ```python
175
+ from timeback_edubridge import (
176
+ EdubridgeError,
177
+ AuthenticationError,
178
+ ForbiddenError,
179
+ NotFoundError,
180
+ ValidationError,
181
+ APIError,
182
+ )
183
+
184
+ try:
185
+ enrollments = await client.enrollments.list(user_id="user-123")
186
+ except AuthenticationError:
187
+ print("Invalid credentials")
188
+ except ForbiddenError:
189
+ print("Access denied")
190
+ except NotFoundError:
191
+ print("Resource not found")
192
+ except ValidationError as e:
193
+ print(f"Invalid request: {e}")
194
+ except APIError as e:
195
+ print(f"API error {e.status_code}: {e}")
196
+ ```
197
+
198
+ ## Environment Variables
199
+
200
+ When using `env` parameter, the client looks for these variables:
201
+
202
+ | Variable | Description |
203
+ |----------|-------------|
204
+ | `{PREFIX}_EDUBRIDGE_BASE_URL` | Base URL for the API |
205
+ | `{PREFIX}_EDUBRIDGE_TOKEN_URL` | OAuth2 token endpoint |
206
+ | `{PREFIX}_EDUBRIDGE_CLIENT_ID` | OAuth2 client ID |
207
+ | `{PREFIX}_EDUBRIDGE_CLIENT_SECRET` | OAuth2 client secret |
208
+
209
+ Without a prefix, it uses the variables without the prefix (e.g., `EDUBRIDGE_BASE_URL`).
@@ -0,0 +1,34 @@
1
+ [project]
2
+ name = "timeback-edubridge"
3
+ version = "0.1.0"
4
+ description = "Python client for the Timeback EduBridge API"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = "MIT"
8
+ authors = [{ name = "Timeback" }]
9
+ keywords = ["timeback", "edubridge", "education", "api", "sdk"]
10
+ classifiers = [
11
+ "Development Status :: 4 - Beta",
12
+ "Intended Audience :: Developers",
13
+ "Programming Language :: Python :: 3.10",
14
+ "Programming Language :: Python :: 3.11",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Programming Language :: Python :: 3.13",
17
+ "Typing :: Typed",
18
+ ]
19
+ dependencies = ["timeback-common>=0.1.0"]
20
+
21
+ [tool.uv.sources]
22
+ timeback-common = { workspace = true }
23
+
24
+ [project.urls]
25
+ Homepage = "https://timeback.ai"
26
+ Documentation = "https://docs.timeback.ai"
27
+ Repository = "https://github.com/timeback-ai/timeback-python"
28
+
29
+ [build-system]
30
+ requires = ["hatchling"]
31
+ build-backend = "hatchling.build"
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["src/timeback_edubridge"]
@@ -0,0 +1,5 @@
1
+ [pytest]
2
+ asyncio_mode = auto
3
+ testpaths = tests
4
+ python_files = test_*.py
5
+ python_functions = test_*
@@ -0,0 +1,158 @@
1
+ """
2
+ Timeback EduBridge Client
3
+
4
+ A Python client for the Timeback EduBridge API with async support.
5
+
6
+ Example:
7
+ ```python
8
+ from timeback_edubridge import EdubridgeClient
9
+
10
+ client = EdubridgeClient(
11
+ base_url="https://api.example.com",
12
+ auth_url="https://auth.example.com/oauth2/token",
13
+ client_id="your-client-id",
14
+ client_secret="your-client-secret",
15
+ )
16
+
17
+ # List enrollments for a user
18
+ enrollments = await client.enrollments.list(user_id="user-123")
19
+
20
+ # Get analytics
21
+ activity = await client.analytics.get_activity(
22
+ student_id="student-123",
23
+ start_date="2025-01-01",
24
+ end_date="2025-01-31",
25
+ )
26
+ ```
27
+ """
28
+
29
+ from .client import EdubridgeClient
30
+ from .exceptions import (
31
+ APIError,
32
+ AuthenticationError,
33
+ EdubridgeError,
34
+ ForbiddenError,
35
+ NotFoundError,
36
+ ValidationError,
37
+ )
38
+ from .types import (
39
+ # Analytics
40
+ ActivityMetricsData,
41
+ AggregatedMetrics,
42
+ # Applications
43
+ Application,
44
+ ApplicationMetrics,
45
+ DailyActivityMap,
46
+ # Base
47
+ DataResponse,
48
+ # Enrollments
49
+ DefaultClass,
50
+ Enrollment,
51
+ EnrollmentCourse,
52
+ EnrollmentFacts,
53
+ EnrollmentGoals,
54
+ EnrollmentMetadata,
55
+ EnrollmentMetrics,
56
+ EnrollmentPeriod,
57
+ EnrollmentPrimaryApp,
58
+ EnrollmentRole,
59
+ EnrollmentSchool,
60
+ EnrollOptions,
61
+ GradeMasteryData,
62
+ GUIDRef,
63
+ HighestGradeMastered,
64
+ # Learning Reports
65
+ MapProfile,
66
+ # Users
67
+ PrimaryOrg,
68
+ ResetGoalsResult,
69
+ Role,
70
+ Status,
71
+ SubjectMetrics,
72
+ # Subject Track
73
+ SubjectTrack,
74
+ SubjectTrackGroup,
75
+ SubjectTrackInput,
76
+ SubjectTrackUpsertInput,
77
+ TimeSaved,
78
+ TimeSpentMetricsData,
79
+ User,
80
+ UserApp,
81
+ UserCredential,
82
+ UserId,
83
+ UserProfile,
84
+ UserRole,
85
+ WeeklyFactRecord,
86
+ WeeklyFacts,
87
+ )
88
+ from .utils import (
89
+ aggregate_activity_metrics,
90
+ normalize_boolean,
91
+ normalize_date,
92
+ normalize_user,
93
+ )
94
+
95
+ __all__ = [
96
+ # Exceptions
97
+ "APIError",
98
+ # Analytics
99
+ "ActivityMetricsData",
100
+ "AggregatedMetrics",
101
+ # Applications
102
+ "Application",
103
+ "ApplicationMetrics",
104
+ "AuthenticationError",
105
+ "DailyActivityMap",
106
+ # Base
107
+ "DataResponse",
108
+ # Enrollments
109
+ "DefaultClass",
110
+ # Client
111
+ "EdubridgeClient",
112
+ "EdubridgeError",
113
+ "EnrollOptions",
114
+ "Enrollment",
115
+ "EnrollmentCourse",
116
+ "EnrollmentFacts",
117
+ "EnrollmentGoals",
118
+ "EnrollmentMetadata",
119
+ "EnrollmentMetrics",
120
+ "EnrollmentPeriod",
121
+ "EnrollmentPrimaryApp",
122
+ "EnrollmentRole",
123
+ "EnrollmentSchool",
124
+ "ForbiddenError",
125
+ "GUIDRef",
126
+ "GradeMasteryData",
127
+ "HighestGradeMastered",
128
+ # Learning Reports
129
+ "MapProfile",
130
+ "NotFoundError",
131
+ # Users
132
+ "PrimaryOrg",
133
+ "ResetGoalsResult",
134
+ "Role",
135
+ "Status",
136
+ "SubjectMetrics",
137
+ # Subject Track
138
+ "SubjectTrack",
139
+ "SubjectTrackGroup",
140
+ "SubjectTrackInput",
141
+ "SubjectTrackUpsertInput",
142
+ "TimeSaved",
143
+ "TimeSpentMetricsData",
144
+ "User",
145
+ "UserApp",
146
+ "UserCredential",
147
+ "UserId",
148
+ "UserProfile",
149
+ "UserRole",
150
+ "ValidationError",
151
+ "WeeklyFactRecord",
152
+ "WeeklyFacts",
153
+ # Utilities
154
+ "aggregate_activity_metrics",
155
+ "normalize_boolean",
156
+ "normalize_date",
157
+ "normalize_user",
158
+ ]