From c558f05cd9da7b08fbd58f41e3b4bd9fcd102dae Mon Sep 17 00:00:00 2001 From: Iamhexi Date: Fri, 4 Oct 2024 20:38:44 +0200 Subject: [PATCH] feat(main): implement adding new materials via MaterialDatabase --- knowledge_verificator/materials.py | 63 +++++++++++++++++++++-- knowledge_verificator/utils/filesystem.py | 20 +++++++ tests/test_filesystem_utils.py | 29 +++++++++++ tests/test_qg.py | 5 +- 4 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 knowledge_verificator/utils/filesystem.py create mode 100644 tests/test_filesystem_utils.py diff --git a/knowledge_verificator/materials.py b/knowledge_verificator/materials.py index 83aeb1f..6bdf274 100644 --- a/knowledge_verificator/materials.py +++ b/knowledge_verificator/materials.py @@ -1,8 +1,11 @@ """Module with tools for managing learning material.""" from dataclasses import dataclass +import os from pathlib import Path +from knowledge_verificator.utils.filesystem import in_directory + @dataclass class Material: @@ -33,14 +36,14 @@ def __init__(self, materials_dir: Path | str) -> None: if isinstance(materials_dir, str): materials_dir = Path(materials_dir) - materials_dir = materials_dir.resolve() - if not materials_dir.exists(): + self.materials_dir = materials_dir.resolve() + if not self.materials_dir.exists(): raise FileNotFoundError( - f'There is no directory under `{materials_dir}`.' + f'There is no directory under `{self.materials_dir}`.' ) self.materials: list[Material] = [] - for directory_path, _, filenames in materials_dir.walk(): + for directory_path, _, filenames in self.materials_dir.walk(): for filename in filenames: path = Path(directory_path).joinpath(filename) material = self.load_material(path) @@ -72,3 +75,55 @@ def load_material(self, path: Path) -> Material: paragraphs=paragraphs, tags=tags, ) + + def add_material(self, material: Material) -> None: + """ + Add a learning material to a database, also material's its + representation in a file. + + Args: + material (Material): Initialised learning material without + existing file representation. + + Raises: + ValueError: Raised if title of a learning material is empty. + FileExistsError: Raised if learning material in a supplied + path already exists. + ValueError: Raised if a supplied path path is outside the + directory for learning materials. + """ + if not material.title: + raise ValueError('Title of a learning material cannot be empty.') + if material.path.exists(): + raise FileExistsError( + 'A file in the provided path already exists. ' + 'Choose a different filename.' + ) + if not in_directory(file=material.path, directory=self.materials_dir): + raise ValueError( + f'A file {os.path.basename(material.path)}' + f' has to be in {self.materials_dir}' + ) + self._create_file_with_material(material=material) + self.materials.append(material) + + def _format_file_content(self, material: Material) -> str: + output = '' + # Format a title. + output += material.title + output += '\n---\n' + + # Format tags. + tags_line = ', '.join('tags') + output += tags_line + '\n' + + # Format content. + content_lines = '\n\n'.join(material.paragraphs) + output += content_lines + '\n\n' + + return output + + def _create_file_with_material(self, material: Material) -> None: + with open(material.path, 'wt', encoding='utf-8') as fd: + file_content = self._format_file_content(material=material) + fd.write(file_content) diff --git a/knowledge_verificator/utils/filesystem.py b/knowledge_verificator/utils/filesystem.py new file mode 100644 index 0000000..17eee76 --- /dev/null +++ b/knowledge_verificator/utils/filesystem.py @@ -0,0 +1,20 @@ +"""Module with filesystem utility functions.""" + +from pathlib import Path + + +def in_directory(file: Path, directory: Path) -> bool: + """ + Determine if a file is located in the supplied directory + or one of its subdirectories. + + Args: + file (Path): Path to a file. + directory (Path): Path to a directory. + + Returns: + bool: Present in a directory or subdirectories (True) or not (False). + """ + return str(directory.resolve()) in str( + file.resolve() + ) and not file.samefile(directory) diff --git a/tests/test_filesystem_utils.py b/tests/test_filesystem_utils.py new file mode 100644 index 0000000..38892c9 --- /dev/null +++ b/tests/test_filesystem_utils.py @@ -0,0 +1,29 @@ +"""Module with tests for filesystem utils.""" + +from pathlib import Path +import pytest + +from knowledge_verificator.utils.filesystem import in_directory + + +@pytest.mark.parametrize( + 'directory, file, exists_there', + ( + ('knowledge_verificator', 'knowledge_verificator/main.py', True), + ('knowledge_verificator', 'tests/test_filesystem_utils.py', False), + ( + 'knowledge_verificator', + 'knowledge_verificator/utils/filesystem.py', + True, + ), + ('knowledge_verificator', 'knowledge_verificator', False), + ), +) +def test_in_directory(file: str, directory: str, exists_there: bool): + """ + Test if a function determining if a file is located inside a directory + or one of its subdirectories works properly. + """ + assert ( + in_directory(file=Path(file), directory=Path(directory)) == exists_there + ) diff --git a/tests/test_qg.py b/tests/test_qg.py index 29898cc..3fc6b18 100644 --- a/tests/test_qg.py +++ b/tests/test_qg.py @@ -8,7 +8,10 @@ @pytest.fixture def qg(): - """Provide non-deterministically initialized instance of the `QuestionGeneration` class.""" + """ + Provide non-deterministically initialized instance of + the `QuestionGeneration` class. + """ set_seed(0) question_generation = QuestionGeneration() return question_generation