def test_initializer_sets_http_client(jupyterhub_api_environ): """ Does initializer set a httpclient instance? """ sut = JupyterHubAPI() assert sut.client is not None assert sut.client != ''
def test_initializer_raises_error_with_jupyterhub_api_token_env_as_missing( monkeypatch, jupyterhub_api_environ): """ Does initializer raise an error when 'JUPYTERHUB_API_TOKEN' is an empty string? """ monkeypatch.setenv('JUPYTERHUB_API_TOKEN', '') with pytest.raises(EnvironmentError): JupyterHubAPI()
def test_initializer_raises_error_with_jupyterhub_api_url_env_as_missing( monkeypatch, jupyterhub_api_environ): """ Does initializer raise an error when 'JUPYTERHUB_API_URL' is an empty None? """ monkeypatch.setenv("JUPYTERHUB_API_URL", "") with pytest.raises(EnvironmentError): JupyterHubAPI()
def test_initializer_sets_headers_to_make_request(jupyterhub_api_environ): """ Does initializer set the api url? """ sut = JupyterHubAPI() assert sut.default_headers is not None assert type(sut.default_headers) is dict assert sut.default_headers[ 'Authorization'] == f'token {os.environ.get("JUPYTERHUB_API_TOKEN")}'
async def test_get_group_uses_request_helper_method_with_correct_values( mock_request, jupyterhub_api_environ): """ Does get_group method use the helper method and pass the correct values? """ sut = JupyterHubAPI() await sut.get_group('some-value') assert mock_request.called mock_request.assert_called_with('groups/some-value')
async def test_create_user_uses_request_helper_method_with_correct_values( mock_request, jupyterhub_api_environ): """ Does create_user method use the helper method and pass the correct values? """ sut = JupyterHubAPI() await sut.create_user('new_user') assert mock_request.called body_usernames = {'usernames': ['new_user']} mock_request.assert_called_with('users/new_user', method='POST', body='')
async def test_create_user_uses_request_helper_method_with_correct_values( mock_request, jupyterhub_api_environ): """ Does create_user method use the helper method and pass the correct values? """ sut = JupyterHubAPI() await sut.create_user("new_user") assert mock_request.called body_usernames = {"usernames": ["new_user"]} mock_request.assert_called_with("users/new_user", method="POST", body="")
async def test_create_group_uses_request_helper_method_with_correct_values( mock_request, jupyterhub_api_environ): """ Does create_group method use the helper method and pass the correct values? """ sut = JupyterHubAPI() await sut.create_group("some-value") assert mock_request.called mock_request.assert_called_with("groups/some-value", body="", method="POST")
async def test_add_group_member_uses_request_helper_method_with_correct_values( mock_request, jupyterhub_api_environ): """ Does add_group_member method use the helper method and pass the correct values? """ sut = JupyterHubAPI() await sut.add_group_member('to_group', 'a_user') assert mock_request.called body_usernames = {'users': ['a_user']} mock_request.assert_called_with('groups/to_group/users', method='POST', body=json.dumps(body_usernames))
async def test_add_group_member_uses_request_helper_method_with_correct_values( mock_request, jupyterhub_api_environ): """ Does add_group_member method use the helper method and pass the correct values? """ sut = JupyterHubAPI() await sut.add_group_member("to_group", "a_user") assert mock_request.called body_usernames = {"users": ["a_user"]} mock_request.assert_called_with("groups/to_group/users", method="POST", body=json.dumps(body_usernames))
async def setup_course_hook( authenticator: Authenticator, handler: RequestHandler, authentication: Dict[str, str] ) -> Dict[str, str]: """ Calls the microservice to setup up a new course in case it does not exist. The data needed is received from auth_state within authentication object. This function assumes that the required k/v's in the auth_state dictionary are available, since the Authenticator(s) validates the data beforehand. This function requires `Authenticator.enable_auth_state = True` and is intended to be used as a post_auth_hook. Args: authenticator: the JupyterHub Authenticator object handler: the JupyterHub handler object authentication: the authentication object returned by the authenticator class Returns: authentication (Required): updated authentication object """ lti_utils = LTIUtils() jupyterhub_api = JupyterHubAPI() # normalize the name and course_id strings in authentication dictionary course_id = lti_utils.normalize_string(authentication['auth_state']['course_id']) nb_service = NbGraderServiceHelper(course_id) username = lti_utils.normalize_string(authentication['name']) lms_user_id = authentication['auth_state']['lms_user_id'] user_role = authentication['auth_state']['user_role'] # register the user (it doesn't matter if it is a student or instructor) with her/his lms_user_id in nbgrader nb_service.add_user_to_nbgrader_gradebook(username, lms_user_id) # TODO: verify the logic to simplify groups creation and membership if user_is_a_student(user_role): # assign the user to 'nbgrader-<course_id>' group in jupyterhub and gradebook await jupyterhub_api.add_student_to_jupyterhub_group(course_id, username) elif user_is_an_instructor(user_role): # assign the user in 'formgrade-<course_id>' group await jupyterhub_api.add_instructor_to_jupyterhub_group(course_id, username) # launch the new (?) grader-notebook as a service setup_response = await register_new_service(org_name=ORG_NAME, course_id=course_id) return authentication
def __init__(self, org: str, course_id: str, domain: str): self.org = org self.course_id = course_id self.domain = domain self.exchange_root = Path(os.environ.get('MNT_ROOT'), self.org, 'exchange') self.grader_name = f'grader-{course_id}' self.grader_root = Path( os.environ.get('MNT_ROOT'), org, 'home', self.grader_name, ) self.course_root = self.grader_root / course_id self.token = token_hex(32) self.client = docker.from_env() self.uid = int(os.environ.get('NB_UID')) self.gid = int(os.environ.get('NB_GID')) self._is_new_setup = False self.jupyterhub_api = JupyterHubAPI()
def test_initializer_sets_api_url_from_env_var(jupyterhub_api_environ): """ Does initializer set the api url correctly? """ sut = JupyterHubAPI() assert sut.api_root_url == os.environ.get('JUPYTERHUB_API_URL')
def test_initializer_sets_api_token_from_env_var(jupyterhub_api_environ): """ Does initializer set the api token correctly? """ sut = JupyterHubAPI() assert sut.token == os.environ.get('JUPYTERHUB_API_TOKEN')
async def setup_course_hook_lti11( handler: RequestHandler, authentication: Dict[str, str], ) -> Dict[str, str]: """ Calls the microservice to setup up a new course in case it does not exist when receiving LTI 1.1 launch requests. The data needed is received from auth_state within authentication object. This function assumes that the required k/v's in the auth_state dictionary are available, since the Authenticator(s) validates the data beforehand. This function requires `Authenticator.enable_auth_state = True` and is intended to be used as a post_auth_hook. Args: handler: the JupyterHub handler object authentication: the authentication object returned by the authenticator class Returns: authentication (Required): updated authentication object """ lti_utils = LTIUtils() jupyterhub_api = JupyterHubAPI() # normalize the name and course_id strings in authentication dictionary username = authentication["name"] lms_user_id = authentication["auth_state"]["user_id"] user_role = authentication["auth_state"]["roles"].split(",")[0] course_id = lti_utils.normalize_string( authentication["auth_state"]["context_label"]) nb_service = NbGraderServiceHelper(course_id, True) # register the user (it doesn't matter if it is a student or instructor) with her/his lms_user_id in nbgrader nb_service.add_user_to_nbgrader_gradebook(username, lms_user_id) # TODO: verify the logic to simplify groups creation and membership if user_is_a_student(user_role): try: # assign the user to 'nbgrader-<course_id>' group in jupyterhub and gradebook await jupyterhub_api.add_student_to_jupyterhub_group( course_id, username) except AddJupyterHubUserException as e: logger.error( "An error when adding student username: %s to course_id: %s with exception %s", (username, course_id, e), ) elif user_is_an_instructor(user_role): try: # assign the user in 'formgrade-<course_id>' group await jupyterhub_api.add_instructor_to_jupyterhub_group( course_id, username) except AddJupyterHubUserException as e: logger.error( "An error when adding instructor username: %s to course_id: %s with exception %s", (username, course_id, e), ) # launch the new grader-notebook as a service try: _ = await register_new_service(org_name=ORG_NAME, course_id=course_id) except Exception as e: logger.error( "Unable to launch the shared grader notebook with exception %s", e) return authentication
async def setup_course_hook(authenticator: Authenticator, handler: RequestHandler, authentication: Dict[str, str]) -> Dict[str, str]: """ Calls the microservice to setup up a new course in case it does not exist. The data needed is received from auth_state within authentication object. This function assumes that the required k/v's in the auth_state dictionary are available, since the Authenticator(s) validates the data beforehand. This function requires `Authenticator.enable_auth_state = True` and is intended to be used as a post_auth_hook. Args: authenticator: the JupyterHub Authenticator object handler: the JupyterHub handler object authentication: the authentication object returned by the authenticator class Returns: authentication (Required): updated authentication object """ lti_utils = LTIUtils() jupyterhub_api = JupyterHubAPI() announcement_port = os.environ.get('ANNOUNCEMENT_SERVICE_PORT') or '8889' org = os.environ.get('ORGANIZATION_NAME') if not org: raise EnvironmentError('ORGANIZATION_NAME env-var is not set') # normalize the name and course_id strings in authentication dictionary course_id = lti_utils.normalize_string( authentication['auth_state']['course_id']) username = lti_utils.normalize_string(authentication['name']) lms_user_id = authentication['auth_state']['lms_user_id'] user_role = authentication['auth_state']['user_role'] # TODO: verify the logic to simplify groups creation and membership if user_role == 'Student' or user_role == 'Learner': # assign the user to 'nbgrader-<course_id>' group in jupyterhub and gradebook await jupyterhub_api.add_student_to_jupyterhub_group( course_id, username) await jupyterhub_api.add_user_to_nbgrader_gradebook( course_id, username, lms_user_id) elif user_role == 'Instructor': # assign the user in 'formgrade-<course_id>' group await jupyterhub_api.add_instructor_to_jupyterhub_group( course_id, username) client = AsyncHTTPClient() data = { 'org': org, 'course_id': course_id, 'domain': handler.request.host, } service_name = os.environ.get( 'DOCKER_SETUP_COURSE_SERVICE_NAME') or 'setup-course' port = os.environ.get('DOCKER_SETUP_COURSE_PORT') or '8000' url = f'http://{service_name}:{port}' headers = {'Content-Type': 'application/json'} response = await client.fetch(url, headers=headers, body=json.dumps(data), method='POST') if not response.body: raise JSONDecodeError('The setup course response body is empty', '', 0) resp_json = json.loads(response.body) logger.debug(f'Setup-Course service response: {resp_json}') # In case of new courses launched then execute a rolling update with jhub to reload our configuration file if 'is_new_setup' in resp_json and resp_json['is_new_setup'] is True: # notify the user the browser needs to be reload (when traefik redirects to a new jhub) url = f'http://localhost:{int(announcement_port)}/services/announcement' jupyterhub_api_token = os.environ.get('JUPYTERHUB_API_TOKEN') headers['Authorization'] = f'token {jupyterhub_api_token}' body_data = { 'announcement': 'A new service was detected, please reload this page...' } await client.fetch(url, headers=headers, body=json.dumps(body_data), method='POST') logger.debug( 'The current jupyterhub instance will be updated by setup-course service...' ) url = f'http://{service_name}:{port}/rolling-update' # our setup-course not requires auth del headers['Authorization'] # WE'RE NOT USING <<<AWAIT>>> because the rolling update should occur later client.fetch(url, headers=headers, body='', method='POST') return authentication