Example #1
0
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))
Example #2
0
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))
Example #3
0
            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)
Example #5
0
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
Example #6
0
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))
Example #7
0
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))
Example #8
0
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
Example #9
0
    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
Example #10
0
            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
Example #11
0
    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
Example #12
0
    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
Example #13
0
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
Example #14
0
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")
Example #15
0
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")
Example #16
0
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")
Example #17
0
def default_request_client(mocker):
    request_client = RequestClient(FAKE_KEY, FAKE_SECRET)
    request_client.get = mocker.MagicMock()
    return request_client
Example #18
0
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
Example #19
0
 def default_request_client():
     return RequestClient(FAKE_KEY, FAKE_SECRET)
Example #20
0
 def it_uses_the_DEFAULT_URL():
     request_client = RequestClient(FAKE_KEY, FAKE_SECRET)
     assert request_client.base_url == DEFAULT_URL