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)
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()
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 ))
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
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
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()
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)
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)
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
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
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()
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()
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.
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
"""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: ")
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.
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)
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
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
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)
def test_paths_exist(self): for path in DataFolder: path = DataFolder.generate_rel_path(path.value) assert os.path.exists(path)
def test_generate_data_path_None(self): """Passing None returns current working directory.""" assert DataFolder.generate_rel_path(None) == Path.cwd()