def rollback_loaded_courses(request_client: RequestClient, creation_response: List[Dict]): """ Delete already loaded courses via the Schoology API. **** Used during testing of the load functionality **** Parameters ---------- request_client : RequestClient A RequestClient initialized for access to the Schoology API creation_response: List[Dict] A list of JSON-like response objects from a successful course load operation """ logger.info( "**** Rolling back %s courses via Schoology API - for testing purposes", len(creation_response), ) ids: List[str] = list( map(lambda course: str(course["id"]), creation_response)) for id in ids: request_client.delete("courses", id) logger.info("**** Deleted course with id %s", id) logger.info("**** Successfully deleted %s courses", len(ids))
def rollback_loaded_discussions(request_client: RequestClient, response_dict: Dict[str, List[Dict]]): """ Delete already loaded discussions via the Schoology API. **** Used during testing of the load functionality **** Parameters ---------- request_client : RequestClient A RequestClient initialized for access to the Schoology API response_dict: Dict[str, List[Dict]] A Dict of mappings from Schoology section id to a list of JSON-like response objects from a successful discussion load operation """ logger.info( "**** Rolling back discussions via Schoology API - for testing purposes" ) for section_id, creation_response in response_dict.items(): logger.info( "**** Deleting %s discussions for section %s", len(creation_response), section_id, ) ids: List[str] = list( map(lambda discussion: str(discussion["id"]), creation_response)) for id in ids: request_client.delete(f"sections/{section_id}/discussions", id) logger.info("**** Deleted discussion with id %s", id) logger.info("**** Successfully deleted %s discussions", len(ids))
def it_should_make_the_get_call( default_request_client: RequestClient, requests_mock): course_id = 1 expected_url_1 = "https://api.schoology.com/v1/courses/1/sections" expected_url_2 = "https://api.schoology.com/v1/courses/2/sections" # Arrange text = '{"section": [{"a":"b"}]}' called = set() def callback(request, context): called.add(request.url) return text requests_mock.get(expected_url_1, reason="OK", status_code=200, text=callback) requests_mock.get(expected_url_2, reason="OK", status_code=200, text=callback) # Act response = default_request_client.get_section_by_course_id( course_id) # Assert assert expected_url_1 in called, "URL 1 not called" assert isinstance( response, PaginatedResult ), "expected response to be a PaginatedResult" assert len( response.current_page_items) == 1, "expected one result"
def _initialize( arguments: MainArguments, ) -> Tuple[ClientFacade, sqlalchemy.engine.base.Engine]: global logger try: request_client: RequestClient = RequestClient(arguments.client_key, arguments.client_secret) db_engine = get_sync_db_engine(arguments.sync_database_directory) facade = ClientFacade(request_client, arguments.page_size, db_engine) # Will generate an exception if directory is not valid os.lstat(arguments.output_directory) if arguments.input_directory: os.lstat(arguments.input_directory) return facade, db_engine except BaseException as e: logger.critical(e) print( "A fatal error occurred, please review the log output for more information.", file=sys.stderr, ) sys.exit(1)
def load_assignments(request_client: RequestClient, section_id: str, assignments: List[Dict]) -> List[Dict]: """ Load a list of assignments via the Schoology API. Parameters ---------- request_client : RequestClient A RequestClient initialized for access to the Schoology API section_id: str The Schoology id of the section that the assignments are associated with assignments: List[Dict] A list of JSON-like assignment objects in a form suitable for submission to the Schoology assignment creation endpoint. Returns ------- List[Dict] A list of JSON-like assignment objects incorporating the response values from the Schoology API, e.g. resource ids and status from individual POSTing """ assert (len(assignments) > 0 and len(assignments) < 51 ), "Number of assignments must be between 1 and 50" logger.info("Creating %s assignments via Schoology API", len(assignments)) post_responses: List[Dict] = [] for assignment in assignments: response: Dict = request_client.post( f"sections/{section_id}/assignments", assignment) post_responses.append(response) logger.info("Successfully created %s assignments", len(post_responses)) return post_responses
def rollback_loaded_enrollments(request_client: RequestClient, response_dict: Dict[str, List[Dict]]): """ Delete already loaded enrollments via the Schoology API. **** Used during testing of the load functionality **** Parameters ---------- request_client : RequestClient A RequestClient initialized for access to the Schoology API response_dict: Dict[str, List[Dict]] A Dict of mappings from Schoology section id to a list of JSON-like response objects from a successful enrollment load operation """ logger.info( "**** Rolling back enrollments via Schoology API - for testing purposes" ) for section_id, creation_response in response_dict.items(): logger.info( "**** Deleting %s enrollments for section %s", len(creation_response), section_id, ) ids: str = ",".join( map(lambda enrollment: str(enrollment["id"]), creation_response)) delete_response = request_client.bulk_delete( "enrollments", f"enrollment_ids={ids}")["enrollments"]["enrollment"] validate_multi_status(delete_response) logger.info("**** Successfully deleted %s enrollments", len(delete_response))
def rollback_loaded_sections(request_client: RequestClient, creation_response: List[Dict]): """ Delete already loaded sections via the Schoology API. **** Used during testing of the load functionality **** Parameters ---------- request_client : RequestClient A RequestClient initialized for access to the Schoology API creation_response: List[Dict] A list of JSON-like response objects from a successful section load operation """ logger.info( "**** Rolling back %s sections via Schoology API - for testing purposes", len(creation_response), ) ids: str = ",".join( map(lambda section: str(section["id"]), creation_response)) delete_response = request_client.bulk_delete( "sections", f"section_ids={ids}")["section"] validate_multi_status(delete_response) logger.info("**** Successfully deleted %s sections", len(delete_response))
def load_discussions(request_client: RequestClient, section_id: str, discussions: List[Dict]) -> List[Dict]: """ Load a list of discussions via the Schoology API. Parameters ---------- request_client : RequestClient A RequestClient initialized for access to the Schoology API section_id: str The Schoology id of the section that the discussions are associated with discussions: List[Dict] A list of JSON-like discussion objects in a form suitable for submission to the Schoology discussion create endpoint. Returns ------- List[Dict] A list of JSON-like discussion objects incorporating the response values from the Schoology API, e.g. resource ids and status from individual POSTing """ logger.info("Creating %s discussions via Schoology API", len(discussions)) post_responses: List[Dict] = [] for discussion in discussions: response: Dict = request_client.post( f"sections/{section_id}/discussions", discussion) post_responses.append(response) logger.info("Successfully created %s discussions", len(post_responses)) return post_responses
def result(requests_mock): section_id = 3324 page_size = 1 expected_url_1 = ( "https://api.schoology.com/v1/sections/3324/enrollments?start=0&limit=1" ) expected_url_2 = ( "https://api.schoology.com/v1/sections/3324/enrollments?start=1&limit=1" ) enrollments_1 = '[{"id": 12345}]' response_1 = ('{"enrollment": ' + enrollments_1 + ',"total": "2","links": {"self": "...","next": "' + expected_url_2 + '"}}') enrollments_2 = '[{"id": 99999}]' response_2 = ('{"enrollment": ' + enrollments_2 + ', "total": "2", "links": {"self": "..."}}') # Arrange def callback(request, context): if request.url == expected_url_1: return response_1 else: return response_2 requests_mock.get(expected_url_1, reason="OK", status_code=200, text=callback) requests_mock.get(expected_url_2, reason="OK", status_code=200, text=callback) client = RequestClient(FAKE_KEY, FAKE_SECRET) # Act result = client.get_enrollments(section_id, page_size) return result
def it_uses_it_for_the_base_url(): # Arrange custom_url = "a_custom_url" # Act request_client = RequestClient(FAKE_KEY, FAKE_SECRET, custom_url) # Assert assert request_client.base_url == custom_url
def result(requests_mock): section_id = 3324 expected_url_1 = "https://api.schoology.com/v1/sections/3324/updates" update_1 = '[{"id": 12345}]' response_1 = ('{"update": ' + update_1 + ',"total": 1, "links": {"self": "ignore"}}') # Arrange requests_mock.get(expected_url_1, reason="OK", status_code=200, text=response_1) client = RequestClient(FAKE_KEY, FAKE_SECRET) # Act result = client.get_section_updates(section_id) return result
def result(requests_mock): section_id = 3324 expected_url_1 = "https://api.schoology.com/v1/sections/3324/attendance" event_1 = '[{"id": 12345}]' response_1 = ('{"date": ' + event_1 + ',"totals": { "total": [{"status":1,"count":1 }]}}') # Arrange requests_mock.get(expected_url_1, reason="OK", status_code=200, text=response_1) client = RequestClient(FAKE_KEY, FAKE_SECRET) # Act result = client.get_attendance(section_id) return result
def get_gradingperiods(request_client: RequestClient) -> List[Dict]: """ Get a list of grading periods via the Schoology API. Parameters ---------- request_client : RequestClient A RequestClient initialized for access to the Schoology API Returns ------- List[Dict] A list of JSON-like grading period objects from a GET all response """ logger.info("Getting grading periods from Schoology API") get_response: List[Dict] = request_client.get( "gradingperiods")["gradingperiods"] logger.info("Successfully retrieved %s grading periods", len(get_response)) return get_response
def load_sections(request_client: RequestClient, course_id: str, sections: List[Dict]) -> List[Dict]: """ Load a list of sections for a course via the Schoology API. Parameters ---------- request_client : RequestClient A RequestClient initialized for access to the Schoology API course_id : str The Schoolgy internal course id the sections are to be associated with sections: List[Dict] A list of JSON-like section objects in a form suitable for submission to the Schoology section creation endpoint. Returns ------- List[Dict] A list of JSON-like section objects incorporating the response values from the Schoology API, e.g. resource ids and status from individual POSTing """ assert (len(sections) > 0 and len(sections) < 51), "Number of sections must be between 1 and 50" logger.info("Creating %s sections via Schoology API", len(sections)) sections_json = {"sections": {"section": sections}} post_response: List[Dict] = request_client.bulk_post( f"courses/{course_id}/sections", sections_json)["section"] validate_multi_status(post_response) logger.info("Successfully created %s sections", len(post_response)) with_schoology_response_df: DataFrame = merge( json_normalize(sections), json_normalize(post_response).drop( ["section_school_code", "grading_periods"], axis=1), left_index=True, right_index=True, ) return with_schoology_response_df.to_dict("records")
def load_enrollments(request_client: RequestClient, section_id: str, enrollments: List[Dict]) -> List[Dict]: """ Load a list of enrollments for a course via the Schoology API. Parameters ---------- request_client : RequestClient A RequestClient initialized for access to the Schoology API section_id : str The Schoolgy internal section id the enrollments are to be associated with enrollments: List[Dict] A list of JSON-like enrollment objects in a form suitable for submission to the Schoology enrollment creation endpoint. Returns ------- List[Dict] A list of JSON-like enrollment objects incorporating the response values from the Schoology API, e.g. resource ids and status from individual POSTing """ assert (len(enrollments) > 0 and len(enrollments) < 51 ), "Number of enrollments must be between 1 and 50" logger.info("Creating %s enrollments via Schoology API", len(enrollments)) enrollments_json = {"enrollments": {"enrollment": enrollments}} post_response: List[Dict] = request_client.bulk_post( f"sections/{section_id}/enrollments", enrollments_json)["enrollments"]["enrollment"] validate_multi_status(post_response) logger.info("Successfully created %s enrollments", len(post_response)) with_schoology_response_df: DataFrame = merge( json_normalize(enrollments), json_normalize(post_response).drop(["uid"], axis=1), left_index=True, right_index=True, ) return with_schoology_response_df.to_dict("records")
def load_courses(request_client: RequestClient, courses: List[Dict]) -> List[Dict]: """ Load a list of courses via the Schoology API. Parameters ---------- request_client : RequestClient A RequestClient initialized for access to the Schoology API courses: List[Dict] A list of JSON-like course objects in a form suitable for submission to the Schoology course creation endpoint. Returns ------- List[Dict] A list of JSON-like course objects incorporating the response values from the Schoology API, e.g. resource ids and status from individual POSTing """ assert (len(courses) > 0 and len(courses) < 51), "Number of courses must be between 1 and 50" logger.info("Creating %s courses via Schoology API", len(courses)) courses_json = {"courses": {"course": courses}} post_response: List[Dict] = request_client.bulk_post( "courses", courses_json)["course"] validate_multi_status(post_response) logger.info("Successfully created %s courses", len(post_response)) with_schoology_response_df: DataFrame = merge( json_normalize(courses), json_normalize(post_response).drop("course_code", axis=1), left_index=True, right_index=True, ) return with_schoology_response_df.to_dict("records")
def default_request_client(mocker): request_client = RequestClient(FAKE_KEY, FAKE_SECRET) request_client.get = mocker.MagicMock() return request_client
from edfi_schoology_extractor.api.request_client import RequestClient from edfi_schoology_extractor.helpers import arg_parser load_dotenv() arguments = arg_parser.parse_main_arguments(argv[1:]) logging.basicConfig( handlers=[ logging.StreamHandler(stdout), ], format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", level="INFO", ) logger = logging.getLogger(__name__) request_client: RequestClient = RequestClient(arguments.client_key, arguments.client_secret) grading_periods = [] try: grading_periods = get_gradingperiods(request_client) except Exception as ex: logger.exception(ex) NUMBER_OF_USERS = 5 users = [] try: users = generate_and_load_users(request_client, NUMBER_OF_USERS) except Exception as ex: logger.exception(ex) NUMBER_OF_COURSES = 5
def default_request_client(): return RequestClient(FAKE_KEY, FAKE_SECRET)
def it_uses_the_DEFAULT_URL(): request_client = RequestClient(FAKE_KEY, FAKE_SECRET) assert request_client.base_url == DEFAULT_URL