예제 #1
0
    def setUp(self):
        """
        Ensure file system and key files with paths in DataFolder exist.

        This only semi-circular (ie x= value; assert value = x), because
        the files are created by the code the app would normally use to
        create these files, and if they're creating them in a place
        other than at the specified paths, the test *should* fail.
        """
        # Create app file system, key files with paths in DataFolder
        data_folder_check()  # App data folders

        # Create dummy registry file if necessary
        self.dummy_registry_file = False
        self.registry_path = DataFolder.generate_rel_path(
            DataFolder.CLASS_REGISTRY.value)
        if not os.path.exists(self.registry_path):
            self.dummy_registry_file = True
            registry_list = ['A_test_class', 'Another_test_class']
            write_registry_to_disk(registry_list)

        # Create dummy settings file if necessary
        self.dummy_settings_file = False
        self.settings_path = DataFolder.generate_rel_path(
            DataFolder.APP_SETTINGS.value)
        if not os.path.exists(self.settings_path):
            self.dummy_settings_file = True
            settings_dict = {'There were': 'no settings set.'}
            write_settings_to_file(settings_dict)
예제 #2
0
 def __init__(
     self,
     default_avatar_path: Path = None,
     database_path: Path = None,
 ):
     self.database_path: Path = (
         database_path or DataFolder.generate_rel_path(
             DataFolder.APP_DATA.value).joinpath('dionysus.db'))
     self.default_avatar_path: Path = (default_avatar_path
                                       or DataFolder.generate_rel_path(
                                           DataFolder.DEFAULT_AVATAR.value))
     # check if db file exists/db has appropriate tables etc
     self._init_db()
예제 #3
0
    def __init__(self,
                 app_data_path: Path = None,
                 class_data_path: Path = None,
                 class_data_file_type: str = None,
                 chart_data_file_type: str = None,
                 default_chart_save_dir: Path = None,
                 default_avatar_path: Path = None,
                 registry_path: Path = None,
                 registry: Registry = None,
                 ):
        """
        Constructs the JSONDatabase object, with defaults for unprovided args.

        NB: default_chart_save_dir has type Optional[Path] because
            definitions.DEFAULT_CHART_SAVE_dir has type Optional[Path] as it
            is uninitialised as None.

        :param app_data_path: Path
        :param class_data_path: Path
        :param class_data_file_type: str
        :param chart_data_file_type: str
        :param default_chart_save_dir: Path
        :param registry_path: Path
        :param registry: Registry
        """
        super().__init__()
        self.app_data_path: Path = (app_data_path
                                    or DataFolder.generate_rel_path(DataFolder.APP_DATA.value))
        self.class_data_path: Path = class_data_path or self.app_data_path.joinpath('class_data')
        self.class_data_file_type: str = class_data_file_type or DEFAULT_CLASSLIST_DATA_FILE_TYPE
        self.default_chart_save_dir: Optional[Path] = (default_chart_save_dir
                                                       or definitions.DEFAULT_CHART_SAVE_DIR)
        self.default_avatar_path: Path = (
                default_avatar_path
                or DataFolder.generate_rel_path(DataFolder.DEFAULT_AVATAR.value))
        self.chart_data_file_type: str = chart_data_file_type or DEFAULT_CHART_DATA_FILE_TYPE
        # Create data paths:
        self.app_data_path.mkdir(parents=True, exist_ok=True)
        self.class_data_path.mkdir(parents=True, exist_ok=True)
        # Initialise class registry:
        self._registry_path: Path = (registry_path
                                     or self.app_data_path.joinpath('class_registry.index'))
        self._registry: Registry = (registry
                                    or Registry(app_data_path=self.app_data_path,
                                                class_data_path=self.class_data_path,
                                                registry_path=self._registry_path,
                                                class_data_file_type=self.class_data_file_type
                                                ))
예제 #4
0
    def get_avatar_path(self, avatar_id: Optional[int]) -> Path:
        """
        Return path to avatar from id.

        Return default avatar if no avatar id.
        Copy avatar image to temp dir using primary key avatar_id as the
        filename.

        Future iteration of chart generation code might facilitate
        returning a binary blob or file-like io.BytesIO object.

        :param avatar_id:
        :return: Path
        """
        if not avatar_id:
            return self.default_avatar_path
        with self.session_scope() as session:
            image_record = session.query(
                self.Avatar).filter(self.Avatar.id == avatar_id).one()
            image = image_record.image

        temp_image_path = Path(
            DataFolder.generate_rel_path(DataFolder.TEMP_DIR.value),
            str(avatar_id))
        temp_image_path.write_bytes(image)
        return temp_image_path
예제 #5
0
    def save_chart_image(self, chart_data_dict: dict, mpl_plt: plt) -> Path:
        """
        Save image in db, and return path to file in temp storage.

        :param chart_data_dict: dict
        :param mpl_plt: matplotlib.pyplot
        :return: Path
        """
        # Get image data:
        image = BytesIO()
        mpl_plt.savefig(image, format='png',
                        dpi=300)  # dpi - 120 comes to 1920*1080, 80 - 1280*720
        image.seek(0)  # Return pointer to start of binary stream.

        # Save image in db
        with self.session_scope() as session:
            chart = session.query(
                self.Chart).filter_by(id=chart_data_dict['chart_id']).one()
            chart.image = image.read()

            session.commit()
        image.seek(0)

        # Save file to temp and pass back Path
        temp_image_path = Path(
            DataFolder.generate_rel_path(DataFolder.TEMP_DIR.value),
            f"{chart_data_dict['chart_name']}.png")
        temp_image_path.write_bytes(image.read())
        return temp_image_path
예제 #6
0
    def __init__(
        self,
        registry_list: List[str] = None,
        app_data_path: Path = None,
        class_data_path: Path = None,
        class_data_file_type: str = None,
        registry_path: Path = None,
    ) -> None:
        """
        Constructs Registry object, with defaults for unprovided args.

        :param registry_list: str
        :param app_data_path: Path
        :param class_data_path: Path
        :param class_data_file_type: str
        :param registry_path: Path
        :return: None
        """
        self.app_data_path: Path = (app_data_path
                                    or DataFolder.generate_rel_path(
                                        DataFolder.APP_DATA.value))
        self.class_data_path: Path = (
            class_data_path or self.app_data_path.joinpath('class_data'))
        self.registry_path: Path = (
            registry_path
            or self.app_data_path.joinpath('class_registry.index'))
        self.class_data_file_type: str = (
            class_data_file_type or json_db.DEFAULT_CLASSLIST_DATA_FILE_TYPE)
        self.list: List[str] = registry_list or self.cache_class_registry()
예제 #7
0
def data_folder_check() -> None:
    """
    Check data folders exist, create them if they do not.

    :return: None
    """

    data_folders = {
        DataFolder.APP_DATA:
        DataFolder.generate_rel_path(DataFolder.APP_DATA.value),
        DataFolder.TEMP_DIR:
        DataFolder.generate_rel_path(DataFolder.TEMP_DIR.value)
    }

    for data_path in data_folders.values():
        data_path.mkdir(parents=True, exist_ok=True)
예제 #8
0
def data_folder_check():
    """
    Check data folders exist, create them if they do not.

    :return: None
    """

    data_folders = {
        DataFolder.APP_DATA:
        DataFolder.generate_rel_path(DataFolder.APP_DATA.value),
        DataFolder.CLASS_DATA:
        DataFolder.generate_rel_path(DataFolder.CLASS_DATA.value),
    }

    for key in data_folders:
        data_folders[key].mkdir(parents=True, exist_ok=True)
예제 #9
0
    def save_chart_image(self, chart_data_dict: dict, mpl_plt: plt) -> Path:
        """
        Save image in db, and return path to file in temp storage.

        :param chart_data_dict: dict
        :param mpl_plt: matplotlib.pyplot
        :return: Path
        """
        # Get image data:
        image = BytesIO()
        mpl_plt.savefig(image, format='png',
                        dpi=300)  # dpi - 120 comes to 1920*1080, 80 - 1280*720
        image.seek(0)  # Return pointer to start of binary stream.

        # Save image in db
        with self._connection() as conn:
            cursor = conn.cursor()
            cursor.execute(
                """
                UPDATE chart
                SET image=?
                WHERE id=?;
                """, (image.read(), chart_data_dict['chart_id']))
            image.seek(0)
            conn.commit()
        conn.close()

        # Save file to temp and pass back Path
        temp_image_path = Path(
            DataFolder.generate_rel_path(DataFolder.TEMP_DIR.value),
            f"{chart_data_dict['chart_name']}.png")
        temp_image_path.write_bytes(image.read())
        return temp_image_path
예제 #10
0
    def get_avatar_path(self, avatar_id: Optional[int]) -> Path:
        """
        Return path to avatar from id.

        Return default avatar if no avatar id.
        Copy avatar image to temp dir using primary key avatar_id as the
        filename.

        Future iteration of chart generation code might facilitate
        returning a binary blob or file-like io.BytesIO object.

        :param avatar_id:
        :return: Path
        """
        if not avatar_id:
            return self.default_avatar_path
        conn = self._connection()
        image = conn.cursor().execute(
            """
                                      SELECT image
                                      FROM avatar
                                      WHERE avatar.id=?;
                                      """, (avatar_id, )).fetchone()[0]

        conn.close()
        temp_image_path = Path(
            DataFolder.generate_rel_path(DataFolder.TEMP_DIR.value),
            str(avatar_id))
        temp_image_path.write_bytes(image)
        return temp_image_path
예제 #11
0
    def test_generate_data_path_defaults(self, relative_path_str):
        """Key app relative paths returned as full paths."""
        os.chdir(ROOT_DIR)
        cwd_path = Path.cwd()
        path_result = DataFolder.generate_rel_path(relative_path_str)

        # Assert relative app paths in generated absolute paths:
        assert relative_path_str in path_result.as_uri()
        # Assert cwd in generated absolute paths:
        # Use .lower() to avoid casing issue (eg Windows user capitalised in cwd_path but not path_result).
        assert cwd_path.as_uri().lower() in path_result.as_uri().lower()
예제 #12
0
    def test_generate_data_path_defaults_dot_value(self, DataFolder_attr,
                                                   relative_path_str):
        """DataFolder attrs generate full paths."""
        os.chdir(ROOT_DIR)
        cwd_path = Path.cwd()

        # path_result = DataFolder.generate_rel_path(DataFolder.attr.value)
        path_result = DataFolder.generate_rel_path(DataFolder_attr)

        assert path_result == cwd_path.joinpath(DataFolder_attr)

        # Assert relative app paths in generated absolute paths:
        assert relative_path_str in path_result.as_uri()
        # Assert cwd in generated absolute paths:
        # Use .lower() to avoid casing issue (eg Windows user capitalised in cwd_path but not path_result).
        assert cwd_path.as_uri().lower() in path_result.as_uri().lower()
예제 #13
0
import argparse
import json
import sys
import time

from pathlib import Path

from dionysus_app.class_ import Class
from dionysus_app.class_functions import write_classlist_to_file
from dionysus_app.data_folder import DataFolder
from dionysus_app.file_functions import load_from_json_file
from dionysus_app.student import Student
from dionysus_app.UI_menus.UI_functions import select_file_dialogue

CLASSLIST_DATA_PATH = DataFolder.generate_rel_path(DataFolder.CLASS_DATA.value)


def main():
    """
    Process arguments, run script based on args.

    :return: None
    """
    run_args = parse_args(sys.argv[1:])

    run_script(run_args)


def parse_args(args: list):
    """
    Takes list of args passed to script.
예제 #14
0
import os

from pathlib import Path
from typing import Union

import definitions

from dionysus_app.data_folder import DataFolder
from dionysus_app.file_functions import move_file
from dionysus_app.UI_menus.settings_functions_UI import (
    user_decides_to_set_default_location,
    user_set_chart_save_folder,
)

APP_DATA = DataFolder.generate_rel_path(DataFolder.APP_DATA.value)
APP_DEFAULT_CHART_SAVE_FOLDER = DataFolder.generate_rel_path(
    DataFolder.APP_DEFAULT_CHART_SAVE_FOLDER.value)
APP_SETTINGS_FILE = DataFolder.generate_rel_path(DataFolder.APP_SETTINGS.value)

CHART_SAVE_FOLDER_NAME = 'dionysus_charts'


def app_start_set_default_chart_save_location():
    """
    Prints welcome statement asking user if they would like to set a
    default chart save location.
    Calls set_default_chart_save_location with user's choice, setting
    save location. Clears screen.

    :return: None
예제 #15
0
"""UI elements for settings"""

from dionysus_app.data_folder import DataFolder
from dionysus_app.UI_menus.UI_functions import (clear_screen,
                                                select_folder_dialogue)

APP_DEFAULT_CHART_SAVE_FOLDER = DataFolder.generate_rel_path(
    DataFolder.APP_DEFAULT_CHART_SAVE_FOLDER.value)


def welcome_set_default_location_message():
    """
    Prints welcome message prompting user to select a folder to save
    charts by default, if desired.

    :return: None
    """
    print('Welcome to dionysus.\n'
          'It looks like this is your first time running the program.\n\n'
          'Would you like to set a default location to save your charts?\n'
          'You can do this later or change your selection in Settings.\n')


def get_user_choice_to_set_location():
    """
    Gets user choice, returning True for some variation of Y or yes,
    otherwise returning False.

    :return: bool
    """
    selection = input("Type 'Y' for Yes or 'N' for No, and press enter: ")
예제 #16
0
    copy_file,
)
from dionysus_app.UI_menus.class_functions_UI import (
    blank_class_dialogue,
    class_data_feedback,
    display_class_selection_menu,
    display_student_selection_menu,
    select_avatar_file_dialogue,
    take_class_selection,
    take_classlist_name_input,
    take_student_name_input,
    take_student_selection,
)
from dionysus_app.UI_menus.UI_functions import clean_for_filename

CLASSLIST_DATA_PATH = DataFolder.generate_rel_path(DataFolder.CLASS_DATA.value)
DEFAULT_AVATAR_PATH = DataFolder.generate_rel_path(
    DataFolder.DEFAULT_AVATAR.value)


def create_classlist():
    classlist_name = take_classlist_name_input(
    )  # TODO: Option to cancel creation at class name entry stage

    setup_class(classlist_name)
    create_classlist_data(classlist_name)


def setup_class(classlist_name):
    """
    Setup class data storage file structure.
예제 #17
0
from dionysus_app.persistence.database import ClassIdentifier
from dionysus_app.UI_menus.class_functions_UI import (
    blank_class_dialogue,
    class_data_feedback,
    create_chart_with_new_class_dialogue,
    display_class_selection_menu,
    display_student_selection_menu,
    select_avatar_file_dialogue,
    take_class_selection,
    take_classlist_name_input,
    take_student_name_input,
    take_student_selection,
)
from dionysus_app.UI_menus.UI_functions import clean_for_filename

DEFAULT_AVATAR_PATH = DataFolder.generate_rel_path(
    DataFolder.DEFAULT_AVATAR.value)


def create_classlist() -> None:
    """
    Create a new class, then give option to create a chart with new class.

    Calls UI elements to collect new class' data, then writes data to persistence.

    :return: None
    """
    classlist_name = take_classlist_name_input(
    )  # TODO: Option to cancel creation at class name entry stage

    new_class: NewClass = compose_classlist_dialogue(classlist_name)
예제 #18
0
 def test_generate_data_path_defaults(self):
     os.chdir(ROOT_DIR)
     cwd_path = Path.cwd().as_uri()  # cwd path OS agnostic for assertion
     for path in self.default_paths:
         path_result = DataFolder.generate_rel_path(path).as_uri()
         assert path in path_result and cwd_path in path_result
예제 #19
0
 def test_generate_data_path_None(self):
     path_result = DataFolder.generate_rel_path(None)
     cwd_path = Path.cwd().as_uri()  # cwd path OS agnostic for assertion
     assert cwd_path in path_result
예제 #20
0
 def test_data_folder_check_default(self):
     os.chdir(os.path.join(os.getcwd(), '.'))
     data_folder_check()
     for path in self.default_paths:
         data_folder_path = DataFolder.generate_rel_path(path)
         assert os.path.exists(data_folder_path)
예제 #21
0
 def test_paths_exist(self):
     for path in DataFolder:
         path = DataFolder.generate_rel_path(path.value)
         assert os.path.exists(path)
예제 #22
0
 def test_generate_data_path_None(self):
     """Passing None returns current working directory."""
     assert DataFolder.generate_rel_path(None) == Path.cwd()