コード例 #1
0
ファイル: fileAPI.py プロジェクト: korpling/machina-callida
def save_learning_result(xapi_statement: XapiStatement) -> LearningResult:
    """Creates a new Learning Result from a XAPI Statement and saves it to the database."""
    learning_result: LearningResult = LearningResultMC.from_dict(
        actor_account_name=xapi_statement.actor.account.name,
        actor_object_type=xapi_statement.actor.object_type.value,
        category_id=xapi_statement.context.context_activities.category[0].id,
        category_object_type=xapi_statement.context.context_activities.
        category[0].object_type.value,
        choices=json.dumps(
            [x.serialize() for x in xapi_statement.object.definition.choices]),
        completion=xapi_statement.result.completion,
        correct_responses_pattern=json.dumps(
            xapi_statement.object.definition.correct_responses_pattern),
        created_time=datetime.utcnow().timestamp(),
        duration=xapi_statement.result.duration,
        extensions=json.dumps(xapi_statement.object.definition.extensions),
        interaction_type=xapi_statement.object.definition.interaction_type,
        object_definition_description=xapi_statement.object.definition.
        description.en_us,
        object_definition_type=xapi_statement.object.definition.type,
        object_object_type=xapi_statement.object.object_type.value,
        response=xapi_statement.result.response,
        score_max=xapi_statement.result.score.max,
        score_min=xapi_statement.result.score.min,
        score_raw=xapi_statement.result.score.raw,
        score_scaled=xapi_statement.result.score.scaled,
        success=xapi_statement.result.success,
        verb_id=xapi_statement.verb.id,
        verb_display=xapi_statement.verb.display.en_us)
    db.session.add(learning_result)
    DatabaseService.commit()
    return learning_result
コード例 #2
0
ファイル: __init__.py プロジェクト: korpling/machina-callida
def init_app_common(cfg: Type[Config] = Config, is_csm: bool = False) -> Flask:
    """ Initializes common Flask parts, e.g. CORS, configuration, database, migrations and custom corpora."""
    spec_dir: str = Config.CSM_DIRECTORY if is_csm else Config.MC_SERVER_DIRECTORY
    connexion_app: FlaskApp = connexion.FlaskApp(
        __name__,
        port=(cfg.CORPUS_STORAGE_MANAGER_PORT if is_csm else cfg.HOST_PORT),
        specification_dir=spec_dir)
    spec_path: str = Config.API_SPEC_CSM_FILE_PATH if is_csm else Config.API_SPEC_MCSERVER_FILE_PATH
    parser = prance.ResolvingParser(
        spec_path, lazy=True, strict=False)  # str(Path(spec_path).absolute())
    parser.parse()
    connexion_app.add_api(parser.specification)
    apply_event_handlers(connexion_app)
    app: Flask = connexion_app.app
    # allow CORS requests for all API routes
    CORS(app)  # , resources=r"/*"
    app.config.from_object(cfg)
    app.app_context().push()
    db.init_app(app)
    migrate.init_app(app, db)
    if is_csm or cfg.TESTING:
        db.create_all()
    if is_csm:
        from mcserver.app.services.databaseService import DatabaseService
        DatabaseService.init_db_alembic()
    from mcserver.app.services.textService import TextService
    TextService.init_proper_nouns_list()
    TextService.init_stop_words_latin()
    if is_csm:
        full_init(app, cfg)
    return app
コード例 #3
0
ファイル: fileAPI.py プロジェクト: korpling/machina-callida
def get(
    id: str, type: FileType, solution_indices: List[int]
) -> Union[ETagResponseMixin, ConnexionResponse]:
    """The GET method for the file REST API. It provides the URL to download a specific file."""
    clean_tmp_folder()
    exercise: Exercise = DatabaseService.query(Exercise,
                                               filter_by=dict(eid=id),
                                               first=True)
    file_name: str = id + "." + str(type)
    mime_type: str = MimeType[type].value
    if exercise is None:
        # try and see if a file is already cached on disk
        if not os.path.exists(os.path.join(Config.TMP_DIRECTORY, file_name)):
            return connexion.problem(404, Config.ERROR_TITLE_NOT_FOUND,
                                     Config.ERROR_MESSAGE_EXERCISE_NOT_FOUND)
        return send_from_directory(Config.TMP_DIRECTORY,
                                   file_name,
                                   mimetype=mime_type,
                                   as_attachment=True)
    exercise.last_access_time = datetime.utcnow().timestamp()
    DatabaseService.commit()
    if solution_indices:
        file_name = id + "-" + str(uuid.uuid4()) + "." + str(type)
    existing_file: DownloadableFile = next(
        (x for x in FileService.downloadable_files
         if x.id + "." + str(x.file_type) == file_name), None)
    if existing_file is None:
        existing_file = FileService.make_tmp_file_from_exercise(
            type, exercise, solution_indices)
    return send_from_directory(Config.TMP_DIRECTORY,
                               existing_file.file_name,
                               mimetype=mime_type,
                               as_attachment=True)
コード例 #4
0
def get(last_update_time: int) -> Union[Response, ConnexionResponse]:
    """The GET method for the corpus list REST API. It provides metadata for all available texts."""
    ui_cts: UpdateInfo = DatabaseService.query(
        UpdateInfo, filter_by=dict(resource_type=ResourceType.cts_data.name), first=True)
    if ui_cts and ui_cts.last_modified_time >= last_update_time / 1000:
        corpora: List[Corpus] = DatabaseService.query(Corpus)
        return NetworkService.make_json_response([x.to_dict() for x in corpora])
    return NetworkService.make_json_response(None)
コード例 #5
0
def delete(cid: int) -> Union[Response, ConnexionResponse]:
    """The DELETE method for the corpus REST API. It deletes metadata for a specific text."""
    corpus: Corpus = DatabaseService.query(Corpus,
                                           filter_by=dict(cid=cid),
                                           first=True)
    if not corpus:
        return connexion.problem(404, Config.ERROR_TITLE_NOT_FOUND,
                                 Config.ERROR_MESSAGE_CORPUS_NOT_FOUND)
    db.session.delete(corpus)
    DatabaseService.commit()
    return NetworkService.make_json_response(True)
コード例 #6
0
ファイル: __init__.py プロジェクト: korpling/machina-callida
def full_init(app: Flask, cfg: Type[Config] = Config) -> None:
    """ Fully initializes the application, including logging."""
    from mcserver.app.services import DatabaseService
    DatabaseService.init_db_update_info()
    from mcserver.app.services.corpusService import CorpusService
    CorpusService.init_corpora()
    from mcserver.app.services import ExerciseService
    ExerciseService.update_exercises(is_csm=True)
    if not cfg.TESTING:
        CorpusService.init_graphannis_logging()
        start_updater(app)
コード例 #7
0
def patch(cid: int, **kwargs) -> Union[Response, ConnexionResponse]:
    """The PUT method for the corpus REST API. It provides updates metadata for a specific text."""
    corpus: Corpus = DatabaseService.query(Corpus,
                                           filter_by=dict(cid=cid),
                                           first=True)
    if not corpus:
        return connexion.problem(404, Config.ERROR_TITLE_NOT_FOUND,
                                 Config.ERROR_MESSAGE_CORPUS_NOT_FOUND)
    for k, v in kwargs.items():
        if v is not None:
            setattr(corpus, k, v)
    DatabaseService.commit()
    return NetworkService.make_json_response(corpus.to_dict())
コード例 #8
0
def post(h5p_data: dict):
    """ The POST method for the H5P REST API. It offers client-side H5P exercises for download as ZIP archives. """
    h5p_form: H5PForm = H5PForm.from_dict(h5p_data)
    language: Language = determine_language(h5p_form.lang)
    exercise: Exercise = DatabaseService.query(
        Exercise, filter_by=dict(eid=h5p_form.eid), first=True)
    if not exercise:
        return connexion.problem(404, Config.ERROR_TITLE_NOT_FOUND,
                                 Config.ERROR_MESSAGE_EXERCISE_NOT_FOUND)
    text_field_content: str = get_text_field_content(exercise,
                                                     h5p_form.solution_indices)
    if not text_field_content:
        return connexion.problem(422, Config.ERROR_TITLE_UNPROCESSABLE_ENTITY,
                                 Config.ERROR_MESSAGE_UNPROCESSABLE_ENTITY)
    response_dict: dict = TextService.json_template_mark_words
    response_dict = get_response(response_dict, language,
                                 TextService.json_template_drag_text, exercise,
                                 text_field_content,
                                 TextService.feedback_template)
    file_name_no_ext: str = str(h5p_form.exercise_type_path)
    file_name: str = f"{file_name_no_ext}.{FileType.ZIP}"
    target_dir: str = Config.TMP_DIRECTORY
    make_h5p_archive(file_name_no_ext, response_dict, target_dir, file_name)
    return send_from_directory(target_dir,
                               file_name,
                               mimetype=MimeType.zip.value,
                               as_attachment=True)
コード例 #9
0
def get(cid: int) -> Union[Response, ConnexionResponse]:
    """The GET method for the corpus REST API. It provides metadata for a specific text."""
    corpus: Corpus = DatabaseService.query(Corpus,
                                           filter_by=dict(cid=cid),
                                           first=True)
    if not corpus:
        return connexion.problem(404, Config.ERROR_TITLE_NOT_FOUND,
                                 Config.ERROR_MESSAGE_CORPUS_NOT_FOUND)
    return NetworkService.make_json_response(corpus.to_dict())
コード例 #10
0
def map_exercise_data_to_database(
        exercise_data: ExerciseData, exercise_type: str, instructions: str,
        xml_guid: str, correct_feedback: str, partially_correct_feedback: str,
        incorrect_feedback: str, general_feedback: str,
        exercise_type_translation: str, search_values: str,
        solutions: List[Solution], conll: str, work_author: str,
        work_title: str, urn: str, language: str):
    """Maps the exercise data so we can save it to the database."""
    # sort the nodes according to the ordering links
    AnnotationService.sort_nodes(graph_data=exercise_data.graph)
    # add content to solutions
    solutions: List[Solution] = adjust_solutions(exercise_data=exercise_data,
                                                 solutions=solutions,
                                                 exercise_type=exercise_type)
    quiz_solutions: str = json.dumps([x.to_dict() for x in solutions])
    tc: TextComplexity = TextComplexityService.text_complexity(
        TextComplexityMeasure.all.name, urn, False, exercise_data.graph)
    new_exercise: Exercise = ExerciseMC.from_dict(
        conll=conll,
        correct_feedback=correct_feedback,
        eid=xml_guid,
        exercise_type=exercise_type,
        exercise_type_translation=exercise_type_translation,
        general_feedback=general_feedback,
        incorrect_feedback=incorrect_feedback,
        instructions=instructions,
        language=language,
        last_access_time=datetime.utcnow().timestamp(),
        partially_correct_feedback=partially_correct_feedback,
        search_values=search_values,
        solutions=quiz_solutions,
        text_complexity=tc.all,
        work_author=work_author,
        work_title=work_title,
        urn=urn)
    # add the mapped exercise to the database
    db.session.add(new_exercise)
    ui_exercises: UpdateInfo = DatabaseService.query(
        UpdateInfo,
        filter_by=dict(resource_type=ResourceType.exercise_list.name),
        first=True)
    ui_exercises.last_modified_time = datetime.utcnow().timestamp()
    DatabaseService.commit()
    return new_exercise
コード例 #11
0
def get(lang: str,
        frequency_upper_bound: int,
        last_update_time: int,
        vocabulary: str = ""):
    """The GET method for the exercise list REST API. It provides metadata for all available exercises."""
    vocabulary_set: Set[str]
    ui_exercises: UpdateInfo = DatabaseService.query(
        UpdateInfo,
        filter_by=dict(resource_type=ResourceType.exercise_list.name),
        first=True)
    if ui_exercises.last_modified_time < last_update_time / 1000:
        return NetworkService.make_json_response([])
    try:
        vc: VocabularyCorpus = VocabularyCorpus[vocabulary]
        vocabulary_set = FileService.get_vocabulary_set(
            vc, frequency_upper_bound)
    except KeyError:
        vocabulary_set = set()
    lang: Language
    try:
        lang = Language(lang)
    except ValueError:
        lang = Language.English
    exercises: List[Exercise] = DatabaseService.query(
        Exercise, filter_by=dict(language=lang.value))
    matching_exercises: List[MatchingExercise] = [
        MatchingExercise.from_dict(x.to_dict()) for x in exercises
    ]
    if len(vocabulary_set):
        for exercise in matching_exercises:
            conll: List[TokenList] = conllu.parse(exercise.conll)
            lemmata: List[str] = [
                tok["lemma"] for sent in conll for tok in sent.tokens
            ]
            exercise.matching_degree = sum(
                (1 if x in vocabulary_set else 0)
                for x in lemmata) / len(lemmata) * 100
    ret_val: List[dict] = [
        NetworkService.serialize_exercise(x, compress=True)
        for x in matching_exercises
    ]
    return NetworkService.make_json_response(ret_val)
コード例 #12
0
def get(eid: str) -> Union[Response, ConnexionResponse]:
    exercise: TExercise = DatabaseService.query(Exercise,
                                                filter_by=dict(eid=eid),
                                                first=True)
    if not exercise:
        return connexion.problem(404, Config.ERROR_TITLE_NOT_FOUND,
                                 Config.ERROR_MESSAGE_EXERCISE_NOT_FOUND)
    ar: AnnisResponse = CorpusService.get_corpus(cts_urn=exercise.urn,
                                                 is_csm=False)
    if not ar.graph_data.nodes:
        return connexion.problem(404, Config.ERROR_TITLE_NOT_FOUND,
                                 Config.ERROR_MESSAGE_CORPUS_NOT_FOUND)
    exercise.last_access_time = datetime.utcnow().timestamp()
    DatabaseService.commit()
    exercise_type: ExerciseType = ExerciseType(exercise.exercise_type)
    ar.solutions = json.loads(exercise.solutions)
    ar.uri = NetworkService.get_exercise_uri(exercise)
    ar.exercise_id = exercise.eid
    ar.exercise_type = exercise_type.value
    return NetworkService.make_json_response(ar.to_dict())
コード例 #13
0
ファイル: fileAPI.py プロジェクト: korpling/machina-callida
def clean_tmp_folder():
    """ Cleans the files directory regularly. """
    ui_file: UpdateInfo = DatabaseService.query(
        UpdateInfo,
        filter_by=dict(resource_type=ResourceType.file_api_clean.name),
        first=True)
    ui_datetime: datetime = datetime.fromtimestamp(ui_file.last_modified_time)
    if (datetime.utcnow() -
            ui_datetime).total_seconds() > Config.INTERVAL_FILE_DELETE:
        for file in [
                x for x in os.listdir(Config.TMP_DIRECTORY)
                if x not in ".gitignore"
        ]:
            file_to_delete_type: str = os.path.splitext(file)[1].replace(
                ".", "")
            file_to_delete: DownloadableFile = next((
                x for x in FileService.downloadable_files
                if x.file_name == file and x.file_type == file_to_delete_type),
                                                    None)
            if file_to_delete is not None:
                FileService.downloadable_files.remove(file_to_delete)
            os.remove(os.path.join(Config.TMP_DIRECTORY, file))
            ui_file.last_modified_time = datetime.utcnow().timestamp()
            DatabaseService.commit()
コード例 #14
0
def get(eid: str, lang: str,
        solution_indices: List[int]) -> Union[Response, ConnexionResponse]:
    """ The GET method for the H5P REST API. It provides JSON templates for client-side H5P exercise layouts. """
    language: Language = determine_language(lang)
    exercise: Exercise = DatabaseService.query(Exercise,
                                               filter_by=dict(eid=eid),
                                               first=True)
    if not exercise:
        return connexion.problem(404, Config.ERROR_TITLE_NOT_FOUND,
                                 Config.ERROR_MESSAGE_EXERCISE_NOT_FOUND)
    text_field_content: str = get_text_field_content(exercise,
                                                     solution_indices)
    if not text_field_content:
        return connexion.problem(422, Config.ERROR_TITLE_UNPROCESSABLE_ENTITY,
                                 Config.ERROR_MESSAGE_UNPROCESSABLE_ENTITY)
    response_dict: dict = TextService.json_template_mark_words
    response_dict = get_response(response_dict, language,
                                 TextService.json_template_drag_text, exercise,
                                 text_field_content,
                                 TextService.feedback_template)
    return NetworkService.make_json_response(response_dict)
コード例 #15
0
 def update_exercises(is_csm: bool) -> None:
     """Deletes old exercises."""
     if DatabaseService.has_table(Config.DATABASE_TABLE_EXERCISE):
         exercises: List[Exercise] = DatabaseService.query(Exercise)
         now: datetime = datetime.utcnow()
         for exercise in exercises:
             exercise_datetime: datetime = datetime.fromtimestamp(
                 exercise.last_access_time)
             # delete exercises that have not been accessed for a while, are not compatible anymore, or contain
             # corrupted / empty data
             if (now - exercise_datetime).total_seconds() > Config.INTERVAL_EXERCISE_DELETE or \
                     not exercise.urn or not json.loads(exercise.solutions):
                 db.session.delete(exercise)
                 DatabaseService.commit()
             # manually add text complexity measures for old exercises
             elif not exercise.text_complexity:
                 ar: AnnisResponse = CorpusService.get_corpus(exercise.urn,
                                                              is_csm=is_csm)
                 tc: TextComplexity = TextComplexityService.text_complexity(
                     TextComplexityMeasure.all.name, exercise.urn, is_csm,
                     ar.graph_data)
                 exercise.text_complexity = tc.all
                 DatabaseService.commit()