From 7c0888ba90a8e44542db3e834790ad936e1a0984 Mon Sep 17 00:00:00 2001 From: Jim Plotts Date: Thu, 27 Mar 2025 10:08:09 -0400 Subject: [PATCH] Rough implementation using kagglesdk This shouldn't impact any existing users, because it depends on an environment variable being set. This will enable us to rollout gradually when we're ready. For now, there are still a few things to be done, primarily improving kagglesdk authorization. http://b/379083750 --- learntools/__init__.py | 2 +- learntools/core/tracking.py | 76 ++++++++++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/learntools/__init__.py b/learntools/__init__.py index 710c86703..6c13d734c 100644 --- a/learntools/__init__.py +++ b/learntools/__init__.py @@ -2,4 +2,4 @@ machine_learning, ml_explainability, ml_insights, ml_intermediate, python, \ sql -__version__ = '0.3.4' +__version__ = '0.3.5' diff --git a/learntools/core/tracking.py b/learntools/core/tracking.py index cad62b6c7..e86a8e4d4 100644 --- a/learntools/core/tracking.py +++ b/learntools/core/tracking.py @@ -1,12 +1,18 @@ import enum from IPython.display import display, Javascript import json - import learntools +import os # If set to True, then echo logged events as output. DEBUG = False +USE_KAGGLESDK = os.environ.get('LEARN_USE_KAGGLE_SDK') == 'True' +if USE_KAGGLESDK: + from kagglesdk import KaggleClient + from kagglesdk.education.types.education_api_service import ApiTrackExerciseInteractionRequest + from kagglesdk.education.types.education_service import LearnExerciseInteractionType, LearnExerciseOutcomeType, LearnExerciseQuestionType + class InteractionType(enum.Enum): CHECK = 1 HINT = 2 @@ -32,7 +38,67 @@ class QuestionType(enum.Enum): trace = '', ) -def track(event): +def interaction_type_to_kagglesdk(event): + switch = { + InteractionType.CHECK: LearnExerciseInteractionType.CHECK, + InteractionType.HINT: LearnExerciseInteractionType.HINT, + InteractionType.SOLUTION: LearnExerciseInteractionType.SOLUTION, + } + value = event['interactionType'] + assert value in switch + return switch.get(value) + +def outcome_type_to_kagglesdk(interaction_type, event): + switch = { + OutcomeType.PASS: LearnExerciseOutcomeType.PASS, + OutcomeType.FAIL: LearnExerciseOutcomeType.FAIL, + OutcomeType.EXCEPTION: LearnExerciseOutcomeType.EXCEPTION, + OutcomeType.UNATTEMPTED: LearnExerciseOutcomeType.UNATTEMPTED, + } + + value = event.get('outcomeType', None) + if value: + assert value in switch + return switch.get(value) + else: + assert interaction_type != LearnExerciseInteractionType.CHECK, "Check events must have an OutcomeType set: {!r}".format(event) + return LearnExerciseOutcomeType.LEARN_EXERCISE_OUTCOME_TYPE_UNSPECIFIED + +def question_type_to_kagglesdk(event): + switch = { + QuestionType.EQUALITYCHECKPROBLEM: LearnExerciseQuestionType.EQUALITY_CHECK_PROBLEM, + QuestionType.CODINGPROBLEM: LearnExerciseQuestionType.CODING_PROBLEM, + QuestionType.FUNCTIONPROBLEM: LearnExerciseQuestionType.FUNCTION_PROBLEM, + QuestionType.THOUGHTEXPERIMENT: LearnExerciseQuestionType.THOUGHT_EXPERIMENT, + } + + question_type = event.get('questionType', None) + if question_type: + assert question_type in switch + return switch.get(question_type) + return None + +def track_using_kagglesdk(event): + request = ApiTrackExerciseInteractionRequest() + request.learn_tools_version = str(learntools.__version__) + request.value_towards_completion = event.get('valueTowardsCompletion', 0.0) + request.interaction_type = interaction_type_to_kagglesdk(event) + request.outcome_type = outcome_type_to_kagglesdk(request.interaction_type, event) + + question_type = question_type_to_kagglesdk(event) + if question_type: + request.question_type = question_type + + # TODO(b/379083750): the following items are still TBD + # - set request.fork_parent_kernel_session_id + # - automatically handle authorization in KaggleClient + # - post the nudge information back to the client + + client = KaggleClient() + result = client.education.education_api_client.track_exercise_interaction(request) + + +def track_using_iframe(event): # TODO: could be nice to put some validation logic here. for k, v in _EVENT_DEFAULTS.items(): event.setdefault(k, v) @@ -65,3 +131,9 @@ def track(event): display(Javascript(debug_js)) display(message) +def track(event): + if USE_KAGGLESDK: + track_using_kagglesdk(event) + else: + track_using_iframe(event) + \ No newline at end of file