From 8c3add01cb3053c3c68fafa8c117c44d7d1f1179 Mon Sep 17 00:00:00 2001 From: Trevor Moy <51927676+trevmoy@users.noreply.github.com> Date: Wed, 27 Mar 2024 16:42:56 -0400 Subject: [PATCH 1/9] initial commit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fead054..63ec1ba 100644 --- a/README.md +++ b/README.md @@ -59,4 +59,4 @@ There are utility methods included to help with the process of creating thread d - `new_document()`: creates a new blank document containing the bare XML tags necessary to create a new thread. - Returns a new `BeautifulSoup` instance for the new document, along with the root document tag (use the document tag to serialize for the API). - `parse_document(content: str)`: parses the content string, which holds the XML content of a thread. - - Similar to `new_document`, returns a new `BeautifulSoup` instance for the parsed document, along with the root document tag. + - Similar to `new_document`, returns a new `BeautifulSoup` instance for the parsed document, along with the root document tag. \ No newline at end of file From 3b39e9720013b6cd304bece28fab947bc419f12e Mon Sep 17 00:00:00 2001 From: Trevor Moy <51927676+trevmoy@users.noreply.github.com> Date: Thu, 28 Mar 2024 14:16:34 -0400 Subject: [PATCH 2/9] Tried adding functionality for lessons with API --- edapi/edapi.py | 49 +++++++++++++++++++-- edapi/types/api_types/endpoints/activity.py | 17 +++++++ edapi/types/api_types/endpoints/lessons.py | 1 + edapi/types/api_types/lesson.py | 20 +++++++++ 4 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 edapi/types/api_types/endpoints/lessons.py create mode 100644 edapi/types/api_types/lesson.py diff --git a/edapi/edapi.py b/edapi/edapi.py index 7d23728..264fa41 100644 --- a/edapi/edapi.py +++ b/edapi/edapi.py @@ -10,7 +10,9 @@ from dotenv import find_dotenv, load_dotenv from requests.compat import urljoin -from .types import EdAuthError, EdError, EditThreadParams, PostThreadParams +from .types import EdAuthError, EdError, EditThreadParams, PostThreadParams # Don't need to import anything for lesson functionality + + from .types.api_types.endpoints.activity import ( API_ListUserActivity_Response, API_ListUserActivity_Response_Item, @@ -27,6 +29,12 @@ from .types.api_types.endpoints.user import API_User_Response from .types.api_types.thread import API_Thread_WithComments, API_Thread_WithUser +# TODO: Figure out if this is needed +# from .types.api_types.endpoints.lessons import ( + +# ) +from .types.api_types.lesson import API_Lesson + ANSI_BLUE = lambda text: f"\u001b[34m{text}\u001b[0m" ANSI_GREEN = lambda text: f"\u001b[32m{text}\u001b[0m" ANSI_RED = lambda text: f"\u001b[31m{text}\u001b[0m" @@ -168,7 +176,7 @@ def get_user_info(self) -> API_User_Response: _throw_error("Failed to get user info.", response.content) - @_ensure_login + def list_user_activity( self, /, @@ -206,7 +214,7 @@ def list_user_activity( f"Failed to list user activity for user {user_id} in course {course_id}.", response.content, ) - + @_ensure_login def list_threads( self, /, course_id: int, *, limit: int = 30, offset: int = 0, sort: str = "new" @@ -232,6 +240,41 @@ def list_threads( f"Failed to list threads for course {course_id}.", response.content ) + + # New function: + # This function is what we need to implement to get the list of lessons + @_ensure_login + def list_lessons(self, course_id: int) -> API_Lesson: + """ + Retrieve the details for lessons. + + GET /api/courses//lessons + + """ + lesson_url = urljoin(API_BASE_URL, f"courses/{course_id}/lessons") + response = self.session.get(lesson_url) + if response.ok: + response_json: API_GetThread_Response = response.json() + return response_json["lesson"] + + _throw_error(f"Failed to get lesson {course_id}.", response.content) + + # Maybe do this later. + # New function: + # def get_lesson(self, lesson_id: int) -> API_Lesson: + # """ + # Retrieve the details for a lesson, given its id. + + # GET /api/lessons/ + # """ + # lesson_url = urljoin(API_BASE_URL, f"lessons/{lesson_id}") + # response = self.session.get(lesson_url) + # if response.ok: + # response_json: API_GetThread_Response = response.json() + # return response_json["lesson"] + + # _throw_error(f"Failed to get lesson {lesson_id}.", response.content) + @_ensure_login def get_thread(self, thread_id: int) -> API_Thread_WithComments: """ diff --git a/edapi/types/api_types/endpoints/activity.py b/edapi/types/api_types/endpoints/activity.py index 6663bf4..ecc9890 100644 --- a/edapi/types/api_types/endpoints/activity.py +++ b/edapi/types/api_types/endpoints/activity.py @@ -17,8 +17,17 @@ class API_ListUserActivity_Response(TypedDict): API_ListUserActivity_Response_Item = Union[ "API_ListUserActivity_Response_CommentItem", "API_ListUserActivity_Response_ThreadItem", + "API_ListUserLesson_Response_LessonItem", ] +class API_ListUserLesson_Response_LessonItem(TypedDict): + """ + Item Type for a lesson, included in the user lesson response + """ + + type: Literal["lesson"] + value: "API_ListUserLesson_Response_LessonItem_Value" + class API_ListUserActivity_Response_CommentItem(TypedDict): """ @@ -38,6 +47,14 @@ class API_ListUserActivity_Response_ThreadItem(TypedDict): value: "API_ListUserActivity_Response_ThreadItem_Value" +class API_ListUserActivity_Response_Item_Value(TypedDict): + lesson_id: int + type: str + + + + + class API_ListUserActivity_Response_CommentItem_Value(TypedDict): id: int type: str diff --git a/edapi/types/api_types/endpoints/lessons.py b/edapi/types/api_types/endpoints/lessons.py new file mode 100644 index 0000000..c213af2 --- /dev/null +++ b/edapi/types/api_types/endpoints/lessons.py @@ -0,0 +1 @@ +# TODO: Implement the lessons endpoint \ No newline at end of file diff --git a/edapi/types/api_types/lesson.py b/edapi/types/api_types/lesson.py new file mode 100644 index 0000000..a28c674 --- /dev/null +++ b/edapi/types/api_types/lesson.py @@ -0,0 +1,20 @@ +from typing import TypedDict, Optional + +# Note: This should be all we need to define the Lesson type. + +class API_Lesson(TypedDict): + """ + Lesson type used in the Ed API. + """ + + # Lesson fields. Not all fields are included. + id: int + title: str + type: str + kind: str + openable: bool # Whether the lesson is open or closed + status: str + due_at: str # ISO 8601 date-time string + created_at: str # ISO 8601 date-time string + + # Optional fields \ No newline at end of file From b76531aa26b0b811d5937896f586a8eec5dde3de Mon Sep 17 00:00:00 2001 From: Trevor Moy <51927676+trevmoy@users.noreply.github.com> Date: Thu, 28 Mar 2024 14:21:54 -0400 Subject: [PATCH 3/9] Updated .toml --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e7b420f..89f8f5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,9 +3,9 @@ requires = ["setuptools"] build-backend = "setuptools.build_meta" [project] -name = "edapi" +name = "edapiwl" version = "0.0.2" -description = "An unofficial integration of the Ed API with Python" +description = "An unofficial integration of the Ed API with Python (with lessons)" readme = "README.md" requires-python = ">=3.9" dependencies = [ @@ -18,4 +18,4 @@ classifiers = [ ] [project.urls] -"Homepage" = "https://github.com/smartspot2/edapi" +"Homepage" = "https://github.com/trevmoy/edapi" From e4185739dbc3245e28bc6ca6bc8694acf32f0aaf Mon Sep 17 00:00:00 2001 From: Trevor Moy <51927676+trevmoy@users.noreply.github.com> Date: Thu, 28 Mar 2024 14:55:48 -0400 Subject: [PATCH 4/9] Implemented lessons.py and added some other things --- .gitignore | 3 +++ edapi/__init__.py | 2 +- edapi/edapi.py | 6 +++-- edapi/types/api_types/endpoints/lessons.py | 27 +++++++++++++++++++++- edapi/types/api_types/lesson.py | 1 + 5 files changed, 35 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 81ec437..08e86d7 100644 --- a/.gitignore +++ b/.gitignore @@ -174,3 +174,6 @@ cython_debug/ # Built Visual Studio Code Extensions *.vsix +# local build +/build +/edapiwl.egg-info/ diff --git a/edapi/__init__.py b/edapi/__init__.py index 83b22bb..1fb6353 100644 --- a/edapi/__init__.py +++ b/edapi/__init__.py @@ -3,4 +3,4 @@ """ # the only default import should be the API class -from .edapi import EdAPI +from .edapi import EdAPIWL diff --git a/edapi/edapi.py b/edapi/edapi.py index 264fa41..9857b59 100644 --- a/edapi/edapi.py +++ b/edapi/edapi.py @@ -26,6 +26,8 @@ API_PutThread_Response, API_PutThread_Response_Thread, ) +from .types.api_types.endpoints.lessons import API_GetLesson + from .types.api_types.endpoints.user import API_User_Response from .types.api_types.thread import API_Thread_WithComments, API_Thread_WithUser @@ -81,7 +83,7 @@ def _throw_error(message: str, error_content: bytes) -> NoReturn: raise EdError({"message": message, "response": error_json}) -class EdAPI: +class EdAPIWL: """ Class for Ed API integration. @@ -254,7 +256,7 @@ def list_lessons(self, course_id: int) -> API_Lesson: lesson_url = urljoin(API_BASE_URL, f"courses/{course_id}/lessons") response = self.session.get(lesson_url) if response.ok: - response_json: API_GetThread_Response = response.json() + response_json: API_GetLesson = response.json() return response_json["lesson"] _throw_error(f"Failed to get lesson {course_id}.", response.content) diff --git a/edapi/types/api_types/endpoints/lessons.py b/edapi/types/api_types/endpoints/lessons.py index c213af2..8c51625 100644 --- a/edapi/types/api_types/endpoints/lessons.py +++ b/edapi/types/api_types/endpoints/lessons.py @@ -1 +1,26 @@ -# TODO: Implement the lessons endpoint \ No newline at end of file +# TODO: Implement the lessons endpoint + +from typing import Any, Optional, TypedDict +from ..user import API_User + + +# === GET /api/lessons/ === +# also used by /api/courses//lessons/?view=1 + +class API_GetLesson(TypedDict): + """ + Response type for GET /api/lessons/. + + Also used by GET /api/courses//lessons/?view=1. + """ + + # Lesson fields. Not all fields are included. + lesson: 53507 # temp class id + id: int + title: str + type: str + kind: str + openable: bool # Whether the lesson is open or closed + status: str + due_at: str # ISO 8601 date-time string + created_at: str # ISO 8601 date-time string diff --git a/edapi/types/api_types/lesson.py b/edapi/types/api_types/lesson.py index a28c674..3b5f495 100644 --- a/edapi/types/api_types/lesson.py +++ b/edapi/types/api_types/lesson.py @@ -8,6 +8,7 @@ class API_Lesson(TypedDict): """ # Lesson fields. Not all fields are included. + lesson: 53507 # temp class id id: int title: str type: str From a8da9de0138460de9efb20f475f7fee799a93200 Mon Sep 17 00:00:00 2001 From: Trevor Moy <51927676+trevmoy@users.noreply.github.com> Date: Thu, 28 Mar 2024 23:33:55 -0400 Subject: [PATCH 5/9] Fixed functionality --- edapi/edapi.py | 4 +++- edapi/types/api_types/endpoints/lessons.py | 19 ++++++------------- edapi/types/api_types/lesson.py | 15 +-------------- pyproject.toml | 4 ++-- 4 files changed, 12 insertions(+), 30 deletions(-) diff --git a/edapi/edapi.py b/edapi/edapi.py index 9857b59..be16d1b 100644 --- a/edapi/edapi.py +++ b/edapi/edapi.py @@ -256,8 +256,10 @@ def list_lessons(self, course_id: int) -> API_Lesson: lesson_url = urljoin(API_BASE_URL, f"courses/{course_id}/lessons") response = self.session.get(lesson_url) if response.ok: + response_json: API_GetLesson = response.json() - return response_json["lesson"] + + return response_json _throw_error(f"Failed to get lesson {course_id}.", response.content) diff --git a/edapi/types/api_types/endpoints/lessons.py b/edapi/types/api_types/endpoints/lessons.py index 8c51625..3454605 100644 --- a/edapi/types/api_types/endpoints/lessons.py +++ b/edapi/types/api_types/endpoints/lessons.py @@ -1,7 +1,8 @@ -# TODO: Implement the lessons endpoint +# TODO: Implement more specific types for the lesson endpoint -from typing import Any, Optional, TypedDict +from typing import TypedDict from ..user import API_User +from ..lesson import API_Lesson # === GET /api/lessons/ === @@ -13,14 +14,6 @@ class API_GetLesson(TypedDict): Also used by GET /api/courses//lessons/?view=1. """ - - # Lesson fields. Not all fields are included. - lesson: 53507 # temp class id - id: int - title: str - type: str - kind: str - openable: bool # Whether the lesson is open or closed - status: str - due_at: str # ISO 8601 date-time string - created_at: str # ISO 8601 date-time string + + lesson: list[API_Lesson] + diff --git a/edapi/types/api_types/lesson.py b/edapi/types/api_types/lesson.py index 3b5f495..3d70e2a 100644 --- a/edapi/types/api_types/lesson.py +++ b/edapi/types/api_types/lesson.py @@ -1,4 +1,4 @@ -from typing import TypedDict, Optional +from typing import TypedDict # Note: This should be all we need to define the Lesson type. @@ -6,16 +6,3 @@ class API_Lesson(TypedDict): """ Lesson type used in the Ed API. """ - - # Lesson fields. Not all fields are included. - lesson: 53507 # temp class id - id: int - title: str - type: str - kind: str - openable: bool # Whether the lesson is open or closed - status: str - due_at: str # ISO 8601 date-time string - created_at: str # ISO 8601 date-time string - - # Optional fields \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 89f8f5e..861739b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "edapiwl" -version = "0.0.2" +version = "0.0.3" description = "An unofficial integration of the Ed API with Python (with lessons)" readme = "README.md" requires-python = ">=3.9" @@ -18,4 +18,4 @@ classifiers = [ ] [project.urls] -"Homepage" = "https://github.com/trevmoy/edapi" +"Homepage" = "https://github.com/trevmoy/edapiwl" From f879db67056ed0617246a0d3e7edf2268da87b2f Mon Sep 17 00:00:00 2001 From: Trevor Moy <51927676+trevmoy@users.noreply.github.com> Date: Thu, 28 Mar 2024 23:47:05 -0400 Subject: [PATCH 6/9] Reverted some info back to original for PR on other repo --- edapi/__init__.py | 2 +- edapi/edapi.py | 2 +- examples/getlessoninfo.py | 8 ++++++++ pyproject.toml | 6 +++--- 4 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 examples/getlessoninfo.py diff --git a/edapi/__init__.py b/edapi/__init__.py index 1fb6353..83b22bb 100644 --- a/edapi/__init__.py +++ b/edapi/__init__.py @@ -3,4 +3,4 @@ """ # the only default import should be the API class -from .edapi import EdAPIWL +from .edapi import EdAPI diff --git a/edapi/edapi.py b/edapi/edapi.py index be16d1b..6563732 100644 --- a/edapi/edapi.py +++ b/edapi/edapi.py @@ -83,7 +83,7 @@ def _throw_error(message: str, error_content: bytes) -> NoReturn: raise EdError({"message": message, "response": error_json}) -class EdAPIWL: +class EdAPI: """ Class for Ed API integration. diff --git a/examples/getlessoninfo.py b/examples/getlessoninfo.py new file mode 100644 index 0000000..72bc2b4 --- /dev/null +++ b/examples/getlessoninfo.py @@ -0,0 +1,8 @@ +from edapi import EdAPI + +ed = EdAPI() +ed.login() + +lesson_info = ed.list_lessons() + +print(f"Lesson 1: {lesson_info['lessons'][0]['title']}") \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 861739b..b430f9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,9 +3,9 @@ requires = ["setuptools"] build-backend = "setuptools.build_meta" [project] -name = "edapiwl" +name = "edapi" version = "0.0.3" -description = "An unofficial integration of the Ed API with Python (with lessons)" +description = "An unofficial integration of the Ed API with Python" readme = "README.md" requires-python = ">=3.9" dependencies = [ @@ -18,4 +18,4 @@ classifiers = [ ] [project.urls] -"Homepage" = "https://github.com/trevmoy/edapiwl" +"Homepage" = "https://github.com/smartspot2/edapi" From 3efae0c6bf4190a56a450728e3a3abdc73097670 Mon Sep 17 00:00:00 2001 From: Trevor Moy <51927676+trevmoy@users.noreply.github.com> Date: Thu, 28 Mar 2024 23:54:09 -0400 Subject: [PATCH 7/9] fixed ensure login --- edapi/edapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edapi/edapi.py b/edapi/edapi.py index 6563732..21a1d8c 100644 --- a/edapi/edapi.py +++ b/edapi/edapi.py @@ -178,7 +178,7 @@ def get_user_info(self) -> API_User_Response: _throw_error("Failed to get user info.", response.content) - + @_ensure_login def list_user_activity( self, /, From abd65a37f88da1cd151c4b8a4e08d44257422995 Mon Sep 17 00:00:00 2001 From: Trevor Moy <51927676+trevmoy@users.noreply.github.com> Date: Fri, 29 Mar 2024 10:04:00 -0400 Subject: [PATCH 8/9] removed uneccessary code for lessons --- edapi/edapi.py | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/edapi/edapi.py b/edapi/edapi.py index 21a1d8c..7b7426e 100644 --- a/edapi/edapi.py +++ b/edapi/edapi.py @@ -10,7 +10,7 @@ from dotenv import find_dotenv, load_dotenv from requests.compat import urljoin -from .types import EdAuthError, EdError, EditThreadParams, PostThreadParams # Don't need to import anything for lesson functionality +from .types import EdAuthError, EdError, EditThreadParams, PostThreadParams from .types.api_types.endpoints.activity import ( @@ -31,10 +31,6 @@ from .types.api_types.endpoints.user import API_User_Response from .types.api_types.thread import API_Thread_WithComments, API_Thread_WithUser -# TODO: Figure out if this is needed -# from .types.api_types.endpoints.lessons import ( - -# ) from .types.api_types.lesson import API_Lesson ANSI_BLUE = lambda text: f"\u001b[34m{text}\u001b[0m" @@ -243,8 +239,7 @@ def list_threads( ) - # New function: - # This function is what we need to implement to get the list of lessons + @_ensure_login def list_lessons(self, course_id: int) -> API_Lesson: """ @@ -263,22 +258,6 @@ def list_lessons(self, course_id: int) -> API_Lesson: _throw_error(f"Failed to get lesson {course_id}.", response.content) - # Maybe do this later. - # New function: - # def get_lesson(self, lesson_id: int) -> API_Lesson: - # """ - # Retrieve the details for a lesson, given its id. - - # GET /api/lessons/ - # """ - # lesson_url = urljoin(API_BASE_URL, f"lessons/{lesson_id}") - # response = self.session.get(lesson_url) - # if response.ok: - # response_json: API_GetThread_Response = response.json() - # return response_json["lesson"] - - # _throw_error(f"Failed to get lesson {lesson_id}.", response.content) - @_ensure_login def get_thread(self, thread_id: int) -> API_Thread_WithComments: """ From 3c1c568a4cb0d396481ebd5c8d609fbb973d728a Mon Sep 17 00:00:00 2001 From: Trevor Moy <51927676+trevmoy@users.noreply.github.com> Date: Fri, 29 Mar 2024 10:04:21 -0400 Subject: [PATCH 9/9] removed more uneccessary code for lessons --- edapi/types/api_types/endpoints/activity.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/edapi/types/api_types/endpoints/activity.py b/edapi/types/api_types/endpoints/activity.py index ecc9890..6e0e932 100644 --- a/edapi/types/api_types/endpoints/activity.py +++ b/edapi/types/api_types/endpoints/activity.py @@ -16,18 +16,9 @@ class API_ListUserActivity_Response(TypedDict): # Union type for both comment and thread items API_ListUserActivity_Response_Item = Union[ "API_ListUserActivity_Response_CommentItem", - "API_ListUserActivity_Response_ThreadItem", - "API_ListUserLesson_Response_LessonItem", + "API_ListUserActivity_Response_ThreadItem" ] -class API_ListUserLesson_Response_LessonItem(TypedDict): - """ - Item Type for a lesson, included in the user lesson response - """ - - type: Literal["lesson"] - value: "API_ListUserLesson_Response_LessonItem_Value" - class API_ListUserActivity_Response_CommentItem(TypedDict): """