Exemplo n.º 1
0
class CsvFileImporter(Importer):
    logger = AppLogger("csv_file_importer").get_logger()

    def __init__(self, tasks):
        super().__init__(tasks)
        self.__tasks = tasks

    def get_tasks_by_id(self, remote_task):
        return self.__tasks.get_task_by_id(remote_task.unique_id)

    @staticmethod
    def convert(obj_list: list) -> List[Task]:
        task_list = list()
        for obj_dict in obj_list:
            task = Task(obj_dict["text"])
            for key, value in obj_dict.items():
                if key == "deleted":
                    value = ast.literal_eval(value)
                    task.deleted = value
                elif key == "due_date":
                    task.due_date.date_string = value
                elif key == "done":
                    value = ast.literal_eval(value)
                    task.due_date.completed = value
                else:
                    setattr(task, key, value)
            task_list.append(task)
        return task_list
Exemplo n.º 2
0
class TaskKeyError(IndexError):
    logger = AppLogger("task_key_error").get_logger()
    msg = "task.key cannot be found"

    def __init__(self):
        super().__init__(self.msg)
        self.logger.error(self.msg)
Exemplo n.º 3
0
class Snapshots:
    logger = AppLogger("snapshots").get_logger()

    def __init__(self, tasks: Tasks):
        self.__tasks = tasks
        self.__task_list = []

    @staticmethod
    def __summarize(task_list: list) -> Snapshot:
        snapshot = Snapshot()
        for task in task_list:
            if task.deleted:
                snapshot.deleted += 1
            elif task.is_completed():
                snapshot.completed += 1
            elif not task.is_completed():
                snapshot.incomplete += 1

            snapshot.due_date = task.due_date.date_string
        snapshot.count = len(task_list)

        return snapshot

    def get_snapshot(self) -> tuple:

        if len(self.__task_list) > 0:
            # snapshot object has index and due_date that are not needed
            # for the summary because there is always one object.
            summary = self.__summarize(self.__task_list)

            snapshot_list = []
            due_date_list = list(
                set([task.due_date.date_string for task in self.__task_list]))
            for index, due_date in enumerate(due_date_list, start=1):
                task_list = self.__tasks.get_tasks_by_date(due_date)
                snapshot = self.__summarize(task_list)
                snapshot.index = index
                snapshot_list.append(snapshot)

            return summary, sorted(snapshot_list, key=lambda sn: sn.due_date)
        else:
            return Snapshot(), list()

    def count_all_tasks(self):
        self.__task_list = self.__tasks.get_object_list()

    def count_tasks_by_due_date_range(self, min_date: str, max_date: str):
        self.__task_list = self.__tasks.get_tasks_within_date_range(
            min_date, max_date)

    def count_tasks_by_due_date(self, due_date: str):
        self.__task_list = self.__tasks.get_tasks_by_date(due_date)

    def count_tasks_by_project(self, project_name: str):
        self.__task_list = self.__tasks.get_tasks_by_project(project_name)
Exemplo n.º 4
0
class File:
    logger = AppLogger("file").get_logger()

    def __init__(self):
        self.__output_dir = CommonVariables().resources_dir

    @staticmethod
    def get_timestamp():
        return time.strftime(CommonVariables().file_name_timestamp)

    def open(self, path):
        if os.path.exists(path):
            self.logger.info(f"Reading file {path}")
            return self.read_file(path)
        else:
            raise FileNotFoundError(f"Path {path} does not exist")

    def save(self, obj_list) -> str:
        if obj_list is not None:
            assert type(obj_list) is list

            file_name = self.get_filename()
            path = self.__make_path(self.__output_dir, file_name)

            if os.path.exists(self.__output_dir):
                self.write_file(path, obj_list)
                self.logger.info(f"Saved results to {path}")
                return path
            else:
                raise FileNotFoundError(f"Path {path} does not exist")

    @staticmethod
    def __make_path(file_path, file_name) -> str:
        if str(file_path).endswith("/"):
            path = f"{file_path}{file_name}"
        else:
            path = f"{file_path}/{file_name}"
        return path

    @abstractmethod
    def write_file(self, path, task_list):
        pass

    @abstractmethod
    def read_file(self, path):
        pass

    @abstractmethod
    def get_filename(self):
        pass
Exemplo n.º 5
0
class DatabaseManager:
    def __init__(self, variables=None):
        self.logger = AppLogger("database_manager").get_logger()
        if variables is None:
            self.variables = CommonVariables()
        else:
            self.variables = variables

    def get_database(self):

        if self.variables.enable_redis:
            redis_db = RedisDatabase(self.variables.redis_host,
                                     self.variables.redis_port)
            if redis_db.exists():
                self.logger.debug("Connecting to redis")
                return redis_db
            else:
                self.logger.info(
                    "Failed to connect to redis. Check redis host and port")
        return JsonFileDatabase()

    def get_tasks_model(self):
        return Tasks(self.get_database())
Exemplo n.º 6
0
class Converter:
    logger = AppLogger("converter").get_logger()

    @staticmethod
    def rfc3339_to_date_string(rfc3339_string) -> str:
        dt = datetime.strptime(rfc3339_string,
                               CommonVariables().rfc3339_date_time_format)
        return Day(dt).to_date_string()

    @staticmethod
    def date_string_to_rfc3339(date_string) -> str:
        variables = CommonVariables()
        if len(date_string) > 0:
            dt = datetime.strptime(date_string, variables.date_format)
            return dt.strftime(variables.rfc3339_date_time_format)
        return date_string
Exemplo n.º 7
0
class Importer:
    logger = AppLogger("importer").get_logger()

    def __init__(self, tasks):
        self.__tasks = tasks

    @abstractmethod
    def get_tasks_by_id(self, remote_task):
        pass

    def import_tasks(self, remote_task_list) -> SyncResultsList:
        """
        Manage task import from Google Tasks Service
        :param remote_task_list:
        :return ImportResultsList:
        """
        assert type(remote_task_list) is list
        sync_results = SyncResultsList()
        for remote_task in remote_task_list:
            assert type(remote_task) is Task

            local_task = self.get_tasks_by_id(remote_task)
            action = ImportAction(local_task, remote_task)

            if action.can_delete():
                self.__tasks.delete(local_task.unique_id)
                sync_results.append(SyncAction.DELETED)

            elif action.can_update():
                self.__tasks.replace(local_task, remote_task)
                sync_results.append(SyncAction.UPDATED)

            elif action.can_insert():
                self.__tasks.append(remote_task)
                sync_results.append(SyncAction.ADDED)

            else:
                sync_results.append(SyncAction.SKIPPED)
                self.logger.debug(f"Skipping local task {remote_task.text}")

        return sync_results
Exemplo n.º 8
0
class Rules:
    """
    Aggregates rule objects and retrieves the result from multiple rules. It also provides an effective way
    to debug the result of import and export rules.
    """
    logger = AppLogger("rules").get_logger()

    def __init__(self, action, state):
        self.action = action
        self.state = state
        self.rule_list = list()
        self.summary_list = list()

    def add(self, rule):
        assert type(rule) is Rule
        self.rule_list.append(rule)

    def get_result(self):
        for rule in self.rule_list:
            if rule.rule_result is False:
                return False
        return True

    @staticmethod
    def decompose(task):
        if task is not None:
            return dict(task)
        return 'None'

    def print_summary(self, local_task, remote_task, rule_result):
        for rule in self.rule_list:
            self.summary_list.append(dict(rule))

        self.logger.debug(
            f"action: {self.action}, state: {self.state}, rule_result: {rule_result}, "
            f"summary: {self.summary_list}, local_task: "
            f"{self.decompose(local_task)}, remote_task: {self.decompose(remote_task)}"
        )
Exemplo n.º 9
0
 def __init__(self, variables=None):
     self.logger = AppLogger("database_manager").get_logger()
     if variables is None:
         self.variables = CommonVariables()
     else:
         self.variables = variables
Exemplo n.º 10
0
class Tasks(Model):
    """
    Main entry point for querying and managing local tasks.
    """
    logger = AppLogger("tasks").get_logger()

    def __init__(self, database):
        super().__init__(database, Task())
        self.__calendar = Calendar()
        self.__vars = CommonVariables()
        self.__date_generator = DateGenerator()

    def add(self, text, label, project, date_expression) -> List[Task]:
        assert type(text) and type(label) and type(project)\
               and type(date_expression) is str
        task_list = list()
        if self.__date_generator.validate_input(date_expression):
            for due_date in self.__date_generator.get_due_dates(
                    date_expression):
                task = Task(text)
                task.label = label
                task.project = project
                task.date_expression = date_expression
                task.due_date = due_date
                self.append(task)
                task_list.append(task)
        else:
            raise AttributeError(
                f"Provided date expression {date_expression} is invalid")

        return task_list

    def append(self, obj: Task):
        assert isinstance(obj, Task)
        return self.append_object(obj)

    def contains_due_date_range(self, task, min_date_string, max_date_string):
        assert isinstance(task, Task)
        assert type(min_date_string) and type(max_date_string) is str

        min_day = Day(
            datetime.strptime(min_date_string, self.__vars.date_format))
        max_day = Day(
            datetime.strptime(max_date_string, self.__vars.date_format))
        if len(task.due_date.date_string) > 0:
            day = Day(
                datetime.strptime(task.due_date.date_string,
                                  self.__vars.date_format))

            if min_day.to_date_time() < day.to_date_time(
            ) < max_day.to_date_time():
                return task

    def get_task(self, func) -> Task:
        for task in self.get_object_list():
            if func(task) is not None:
                self.logger.debug(
                    f"Retrieved task by index: {task.index}, text: {task.text}"
                )
                return task

    def get_task_list(self):
        return sorted(self.get_object_list(),
                      key=lambda task: task.due_date.date_string)

    def get_tasks_containing_text(self, value) -> List[Task]:
        """
        Selects all tasks that with a text value that contain the provided value
        :param value:
        :return: list of Task
        """
        assert type(value) is str
        return [
            task for task in self.get_task_list()
            if str(value).lower() in str(task.text).lower()
        ]

    def get_tasks_matching_text(self, value) -> List[Task]:
        """
        Selects all tasks with a text value that matches the provided value
        :param value:
        :return:
        """
        assert type(value) is str
        return [
            task for task in self.get_task_list()
            if str(value.lower() == str(task.text).lower())
        ]

    def get_task_by_index(self, index: int) -> Task:
        assert type(index) is int
        return self.get_task(lambda task: task
                             if task.index == index else None)

    def get_task_by_external_id(self, external_id: int) -> Task:
        assert type(external_id) is str
        return self.get_task(lambda task: task
                             if task.external_id == external_id else None)

    def get_task_by_id(self, task_id: str) -> Task:
        assert type(task_id) is str
        return self.get_task(lambda task: task
                             if task.unique_id == task_id else None)

    def get_task_by_name(self, task_name: str) -> Task:
        assert type(task_name) is str
        return self.get_task(lambda task: task
                             if task.text == task_name else None)

    def get_tasks_by_date(self, date_expression: str) -> List[Task]:
        assert type(date_expression) is str
        task_list = list()
        for due_date in self.__date_generator.get_due_dates(date_expression):
            for task in self.get_task_list():
                if task.due_date.date_string == due_date.date_string:
                    task_list.append(task)
        return task_list

    def get_tasks_within_date_range(self, min_date_string: str,
                                    max_date_string: str) -> List[Task]:
        assert type(min_date_string) is str
        assert type(max_date_string) is str
        return [
            task
            for task in self.get_task_list() if self.contains_due_date_range(
                task, min_date_string, max_date_string)
        ]

    def get_tasks_by_status(self, is_completed: bool) -> List[Task]:
        assert type(is_completed) is bool

        if is_completed:
            return [
                task for task in self.get_task_list() if task.is_completed()
            ]
        else:
            return [
                task for task in self.get_task_list()
                if not task.is_completed()
            ]

    def get_tasks_by_project(self, project: str) -> List[Task]:
        assert type(project) is str
        return self.get_list_by_type("project", project)

    def get_tasks_by_label(self, label: str) -> List[Task]:
        assert type(label) is str
        return self.get_list_by_type("label", label)

    def get_filtered_list(self) -> List[Task]:
        return [task for task in self.get_task_list() if not task.deleted]

    def delete(self, task_id: str) -> Task:
        assert type(task_id) is str

        task = self.get_task_by_id(task_id)
        if task is not None:
            task.deleted = True
            self.replace_object(task.index, task)
            return task
        else:
            raise TaskKeyError()

    def undelete(self, task_id: str) -> Task:
        assert type(task_id) is str

        task = self.get_task_by_id(task_id)
        if task is not None:
            task.deleted = False
            self.replace_object(task.index, task)
            return task
        else:
            raise TaskKeyError()

    def complete(self, task_id: str) -> Task:
        assert type(task_id) is str

        task = self.get_task_by_id(task_id)
        if task is not None:
            task.complete()
            self.replace_object(task.index, task)
            return task
        else:
            raise TaskKeyError()

    def reset(self, task_id: str) -> Task:
        """
        Resets the due date to today on the selected task
        :param task_id:
        :return:
        """
        assert type(task_id) is str

        task = self.get_task_by_id(task_id)
        if task is not None:
            due_date = DueDate()
            due_date.completed = False
            due_date.date_string = Today().to_date_string()
            task.due_date = due_date
            self.replace_object(task.index, task)
            return task
        else:
            raise TaskKeyError()

    def replace(self, local_task: Task, remote_task: Task) -> Task:
        assert isinstance(remote_task, Task)
        assert isinstance(local_task, Task)

        remote_task.index = local_task.index
        remote_task.unique_id = local_task.unique_id
        remote_task.due_date = local_task.due_date

        self.replace_object(local_task.index, remote_task)
        self.logger.debug(
            f"Replaced local_task: {dict(local_task)} with remote_task: {dict(remote_task)}"
        )
        return remote_task

    def edit(self, index: int, text: str, label: str, project: str,
             date_expression: str) -> Task:

        task = self.get_task_by_index(index)
        if task is not None:
            task.text = text
            task.project = project
            task.label = label

            due_date = self.__date_generator.get_due_date(date_expression)
            if due_date is not None:
                task.date_expression = date_expression
                task.due_date.date_string = due_date.date_string

            self.replace_object(task.index, task)
            return task
        else:
            raise TaskKeyError()

    def reschedule(self, today) -> None:
        assert type(today) is Today or Day
        task_list = self.get_task_list()
        for task in task_list:
            if self.__calendar.is_past(
                    task.due_date, today
            ) and task.due_date.completed is False and task.deleted is False:
                task.due_date.date_string = today.to_date_string()
        self.update_objects(task_list)

    def get_list_by_type(self,
                         parameter_name: str,
                         value: str,
                         task_list=None) -> list:
        """
        Returns list of tasks when a task parameter (ie. project, text, label) matches
        a single value.
        :param parameter_name:
        :param value:
        :param task_list:
        :return:
        """
        assert type(parameter_name) is str
        assert type(value) is str

        if task_list is None:
            task_list = self.get_task_list()
        else:
            assert type(task_list) is list

        return list(
            filter(lambda t: getattr(t, parameter_name) == value, task_list))

    def __sort(self, parameter_name: str) -> list:
        assert type(parameter_name) is str
        return [
            t for t in sorted(self.get_task_list(),
                              key=lambda t: getattr(t, parameter_name))
        ]

    def get_label_list(self) -> List[str]:
        return self.unique("label", self.get_task_list())

    def get_project_list(self) -> List[str]:
        return self.unique("project", self.get_task_list())

    def get_due_date_list(self) -> List[str]:
        return list(
            set([task.due_date.date_string for task in self.get_task_list()]))

    @staticmethod
    def unique(parameter_name: str, task_list: list) -> List[str]:
        assert type(parameter_name) is str
        assert type(task_list) is list
        unique_set = set([getattr(task, parameter_name) for task in task_list])
        return sorted(list(unique_set))

    def clear(self):
        self.clear_objects()
Exemplo n.º 11
0
class Client:
    """
    Base client facade that that provides access to all the application features. It integrates the import/export, tasks,
    snapshot, common variables, and date generator classes. It also makes it possible to support additional clients.
    Currently only the console client is supported, but a rest api could also extend this class.
    """
    logger = AppLogger("client").get_logger()

    def __init__(self, db_manager):
        assert isinstance(db_manager, DatabaseManager)

        self.tasks = db_manager.get_tasks_model()
        self.__date_generator = DateGenerator()
        self.__variables = CommonVariables()

    @abstractmethod
    def display_tasks(self, task_list: list):
        pass

    @abstractmethod
    def display_snapshots(self, snapshots: Snapshots, **kwargs):
        pass

    @abstractmethod
    def display_invalid_index_error(self, index: int):
        pass

    def edit_task(self, index: int, text: str, label: str, project: str,
                  date_expression: str) -> List[Task]:
        """
        Edits an existing task by replacing string values. None are allowed
        and handled by the Task object.
        :param index: integer starting at 0
        :param text: text string describing the task
        :param label: label name of the task
        :param project: project name of the task
        :param date_expression: Must be one of [today, tomorrow, m-s, every *, month / day, etc].
        :return: Task
        """
        assert type(index) is int
        assert type(text) is str
        assert type(project) is str
        assert type(label) is str
        assert type(date_expression) is str

        try:
            if self.__date_generator.validate_input(date_expression) is False:
                self.logger.info(
                    f"Provided due date {date_expression} is invalid")
            else:
                task = self.tasks.edit(index, text, label, project,
                                       date_expression)
                return self.display_tasks([task])
        except TaskKeyError:
            self.display_invalid_index_error(index)

    def get_task(self, task_index: int) -> List[Task]:
        task = self.tasks.get_task_by_index(int(task_index))
        return self.display_tasks([task])

    def filter_tasks_by_today(self) -> List[Task]:
        date_string = Today().to_date_string()
        task_list = self.tasks.get_tasks_by_date(date_string)
        return self.display_tasks(task_list)

    def filter_tasks_by_due_date(self, date_string: str) -> List[Task]:
        task_list = self.tasks.get_tasks_by_date(date_string)
        return self.display_tasks(task_list)

    def filter_tasks_by_due_date_range(self, min_date: str,
                                       max_date: str) -> List[Task]:
        task_list = self.tasks.get_tasks_within_date_range(min_date, max_date)
        return self.display_tasks(task_list)

    def filter_tasks_by_status(self, status: str) -> List[Task]:
        assert status in ["incomplete", "complete"]
        if status == "incomplete":
            task_list = self.tasks.get_tasks_by_status(is_completed=False)
        else:
            task_list = self.tasks.get_tasks_by_status(is_completed=True)
        return self.display_tasks(task_list)

    def filter_tasks_by_project(self, project: str) -> List[Task]:
        assert type(project) is str
        task_list = self.tasks.get_tasks_by_project(project)
        return self.display_tasks(task_list)

    def filter_tasks_by_label(self, label: str) -> List[Task]:
        assert type(label) is str
        task_list = self.tasks.get_tasks_by_label(label)
        return self.display_tasks(task_list)

    def filter_tasks_by_text(self, text: str) -> List[Task]:
        assert type(text) is str
        task_list = self.tasks.get_tasks_containing_text(text)
        return self.display_tasks(task_list)

    def group_tasks_by_project(self) -> List[Task]:
        task_list = list()
        for project in self.get_unique_project_list():
            for task in self.tasks.get_tasks_by_project(project):
                task_list.append(task)
        return self.display_tasks(task_list)

    def group_tasks_by_due_date(self) -> List[Task]:
        task_list = list()
        for due_date_string in self.__get_unique_due_date_list():
            for task in self.tasks.get_tasks_by_date(due_date_string):
                task_list.append(task)
        return self.display_tasks(task_list)

    def group_tasks_by_label(self) -> List[Task]:
        task_list = list()
        for label in self.get_unique_label_list():
            for task in self.tasks.get_tasks_by_label(label):
                task_list.append(task)
        return self.display_tasks(task_list)

    def get_unique_label_list(self) -> List[str]:
        """Returns a list of labels from the tasks."""
        return self.tasks.get_label_list()

    def get_unique_project_list(self) -> List[str]:
        """Returns list of project names from the tasks. """
        return self.tasks.get_project_list()

    def __get_unique_due_date_list(self) -> List[str]:
        """Returns list of due_date strings from the tasks."""
        return self.tasks.get_due_date_list()

    def count_all_tasks(self, **kwargs) -> List[Snapshot]:
        snapshots = Snapshots(self.tasks)
        snapshots.count_all_tasks()
        return self.display_snapshots(snapshots, **kwargs)

    def count_tasks_by_due_date_range(self, min_date: str, max_date: str,
                                      **kwargs) -> List[Snapshot]:
        assert type(min_date) and type(max_date) is str

        snapshots = Snapshots(self.tasks)
        snapshots.count_tasks_by_due_date_range(min_date, max_date)
        return self.display_snapshots(snapshots, **kwargs)

    def count_tasks_by_due_date(self, due_date: str,
                                **kwargs) -> List[Snapshot]:
        assert type(due_date) is str

        snapshots = Snapshots(self.tasks)
        snapshots.count_tasks_by_due_date(due_date)
        return self.display_snapshots(snapshots, **kwargs)

    def count_tasks_by_project(self, project_name: str,
                               **kwargs) -> List[Snapshot]:
        assert type(project_name) is str

        snapshots = Snapshots(self.tasks)
        snapshots.count_tasks_by_project(project_name)
        return self.display_snapshots(snapshots, **kwargs)

    def reschedule_tasks(self, today=Today()):
        self.tasks.reschedule(today)

    def remove_all_tasks(self):
        self.tasks.clear()

    def set_default_variables(self, **kwargs):
        """
        Sets the defaults variables to the variables.ini file.
        :param variable_dict: Contains key value pairs matching the
        properties in Variables class
        :return: None
        """
        assert type(kwargs) is dict

        for key, value in kwargs.items():
            if hasattr(self.__variables, key):
                setattr(self.__variables, key, value)

    @staticmethod
    def get_duration(start_datetime):
        """
        Gets a formatted time string using the provided datetime object
        :param start_datetime:
        :return: time string
        """
        end_datetime = datetime.now()
        total_seconds = (end_datetime - start_datetime).total_seconds()
        hours, remainder = divmod(total_seconds, 3600)
        minutes, seconds = divmod(remainder, 60)
        return '{:02}m:{:02}s'.format(int(minutes), int(seconds))

    @staticmethod
    def get_variables_list():
        return dict(CommonVariables()).items()
Exemplo n.º 12
0
class GenericDatabase(ABC):
    """Generic base class to support Yaml, Json file databases and redis."""
    logger = AppLogger("database").get_logger()

    def __init__(self):
        self.resources_dir = CommonVariables().resources_dir

    @abstractmethod
    def initialize(self, obj):
        """
        Executes initialization activities such as constructing the file name if it is a
        file database or selects the logical redis database
        :param obj: Object that subclasses DatabaseObject
        :return: None
        """
        pass

    @abstractmethod
    def replace(self, obj):
        """
        Replaces specific object using obj.index.
        :param index: integer
        :param obj: Object that subclasses DatabaseObject
        :return: obj
        """
        pass

    @abstractmethod
    def append(self, obj):
        """
        Adds the object to end of the list of objects and assigns a new index id based
        on the number of existing objects. Objects are preserved using a soft delete method.
        Only the deleted parameter is changed from False to True. The index id is unique
        and should not be re-used.
        :param obj:
        :return: obj
        """
        pass

    @abstractmethod
    def set(self, obj_list):
        """
        Acts on objects in a list. It replaces an object when the index matches OR adds to the end
        of the list when the index does not match an existing one.
        :param obj_list:
        :return:
        """
        pass

    @abstractmethod
    def get(self) -> List[dict]:
        """
        Converts a stored database object (ie. json object) to a python dictionary. Use the to_object_list
        method to convert the dictionary to an object that implements the DatabaseObject class.
        :return:
        """
        pass

    @abstractmethod
    def exists(self) -> bool:
        pass

    @abstractmethod
    def clear(self):
        pass

    @staticmethod
    def contains_dict(dict_list) -> bool:
        if dict_list is not None:
            assert type(dict_list) is list
            if len(dict_list) >= 1:
                assert type(dict_list[0]) is dict
                return True
        return False

    @staticmethod
    def contains_database_object(obj_list) -> bool:
        if obj_list is not None:
            assert type(obj_list) is list
            if len(obj_list) >= 1:
                if isinstance(obj_list[0], DatabaseObject):
                    return True
                else:
                    raise ValueError(
                        "Provided obj must subclass DatabaseObject")
        return False

    @staticmethod
    def to_object_list(dict_list, obj) -> List:
        if dict_list is not None:
            assert isinstance(obj, DatabaseObject)
            assert GenericDatabase.contains_dict(dict_list)

            obj_list = list()
            for obj_dict in dict_list:
                obj = copy(obj)
                obj = obj.deserialize(obj_dict)
                obj_list.append(obj)
            return obj_list
        else:
            return []

    @staticmethod
    def to_dict_list(obj_list) -> List:
        if obj_list is not None:
            assert GenericDatabase.contains_database_object(obj_list)
            try:
                return [dict(obj) for obj in obj_list]
            except TypeError as ex:
                GenericDatabase.logger.error(
                    f"{ex}. Provided obj must contain __iter__ method")
                raise ex
        return []

    @staticmethod
    def to_json(obj):
        assert isinstance(obj, DatabaseObject)
        try:
            return json.dumps(dict(obj))
        except TypeError as ex:
            GenericDatabase.logger.error(
                f"{ex}. Provided obj must contain __iter__ method")
            raise ex

    @staticmethod
    def get_last_updated() -> str:
        return datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S")

    def get_file_path(self, obj, file_ext) -> str:
        assert isinstance(obj, DatabaseObject)
        path = f"{self.resources_dir}{str(obj.object_name).lower()}_db.{file_ext}"
        if not self.file_exists(path):
            os.makedirs(self.resources_dir, 0o777, exist_ok=True)
        return path

    @staticmethod
    def file_exists(path) -> bool:
        return os.path.exists(path)

    def remove_file(self, path):
        try:
            if self.file_exists(path):
                os.remove(path)
        except FileNotFoundError as ex:
            self.logger.error(f"Cannot remove database file {ex}")
            raise

    @staticmethod
    def get_list_index(obj, dict_list):
        assert type(dict_list) is list
        assert isinstance(obj, DatabaseObject)
        index_list = [
            index for index, obj_dict in enumerate(dict_list, start=0)
            if obj_dict["index"] == obj.index
        ]
        if len(index_list) == 1:
            return index_list[0]

    @staticmethod
    def set_unique_id(obj) -> DatabaseObject:
        """
        A unique id should only be assigned once when the object is appended or replaced.
        :param obj: Object that subclasses DatabaseObject
        :return:
        """
        assert isinstance(obj, DatabaseObject)
        if obj.unique_id is None:
            obj.unique_id = uuid.uuid4().hex
        return obj

    @staticmethod
    def get_last_index(dict_list) -> int:
        if dict_list is not None:
            count = len(dict_list)
            if count > 0:
                return count + 1
            else:
                return 1
Exemplo n.º 13
0
class RedisExperimentDatabase(GenericDatabase):
    """
    Manages saving and retrieving objects to a redis database. Retrieve method should be called
    before save to maintain a consistent state.
    """

    logger = AppLogger("redis_experiment_database").get_logger()

    def __init__(self, host, port):
        super().__init__()
        self.host = host
        self.port = port
        self.db = None

    def build_db_list(self):
        # Max number of keys in a redis logical databases is 16. Scan all
        # logical databases to identify what object belongs in each.
        db_list = []
        for index in range(0, 16):
            db = redis.Redis(host=self.host, port=self.port, db=index)
            keys = db.keys("1:*")
            if len(keys) == 1:
                key = str(keys[0].decode("utf-8")).lstrip("1:")
                db_list.append((db, key))
            else:
                db_list.append((db, None))
        return db_list

    def initialize(self, obj, test_mode=False):
        """
        Retrieves the redis db id from the object so it can be saved or updated.
        :param obj: Object that subclasses DatabaseObject
        :param test_mode: Unsupported
        :return: None
        """
        self.db = redis.Redis(host=self.host,
                              port=self.port,
                              db=obj.get_redis_db_id())

    @staticmethod
    def __key(obj):
        return f"{obj.index}:{obj.object_name}"

    def replace(self, obj):
        assert isinstance(obj, DatabaseObject)
        assert obj.index > 0
        self.set([obj])

    def append(self, obj):
        assert isinstance(obj, DatabaseObject)
        self.set([obj])

    def set(self, obj_list):
        """
        Updates or inserts new objects that inherit from DatabaseObject. When key matches existing key an update
        occurs, but if there is no match then a new key is created.
        :param obj_list: List containing class objects
        :return: None
        """
        assert self.contains_database_object(obj_list)
        if self.exists():
            for obj in obj_list:
                if not self.db.exists(self.__key(obj)):
                    obj.index = self.get_last_index(self.db.keys())

                obj = self.set_unique_id(obj)
                obj.last_updated = self.get_last_updated()
                self.db.hset(self.__key(obj), "last_updated", obj.last_updated)
                self.db.hset(self.__key(obj), "data", self.to_json(obj))

    def get(self) -> List[dict]:
        if self.db is not None:
            return [
                json.loads(self.db.hget(key, "data"))
                for key in sorted(self.db.keys())
            ]
        else:
            return []

    def exists(self):
        db = redis.Redis(host=self.host, port=self.port, db=0)
        return db.ping()

    def clear(self):
        for db_item in self.build_db_list():
            for key in db_item[0].keys():
                db_item[0].delete(key)
Exemplo n.º 14
0
class JsonFileDatabase(GenericDatabase):
    """
    Manages saving and retrieving tasks as dict object to a json file
    located in the resource directory.
    """

    logger = AppLogger("json_file_database").get_logger()

    def __init__(self):
        super().__init__()
        self.path = None

    def initialize(self, obj):
        """
        Constructs the db file path
        :param obj: Class object that implements DatabaseObject
        :return: None
        """
        self.path = self.get_file_path(obj, "json")

    def replace(self, obj):
        assert isinstance(obj, DatabaseObject)
        assert obj.index > 0

        dict_list = self.get()
        if dict_list is not None:
            index = self.get_list_index(obj, dict_list)
            if index is not None:
                obj = self.set_unique_id(obj)
                obj.last_updated = self.get_last_updated()
                dict_list[index] = dict(obj)
                self.save(dict_list)

    def append(self, obj):
        assert isinstance(obj, DatabaseObject)

        dict_list = self.get()
        if dict_list is not None:
            obj.index = self.get_last_index(dict_list)
            obj = self.set_unique_id(obj)
            obj.last_updated = self.get_last_updated()
            dict_list.append(dict(obj))
            self.save(dict_list)
        else:
            obj.index = 1
            obj = self.set_unique_id(obj)
            obj.last_updated = self.get_last_updated()
            self.save([dict(obj)])

    def save(self, dict_list):
        with open(self.path, 'w') as outfile:
            self.logger.debug("Saved json database")
            ujson.dump(dict_list, outfile)

    def set(self, obj_list):
        self.save(self.to_dict_list(obj_list))

    def get(self) -> List[dict]:
        if self.exists():
            self.logger.debug("Retrieved json database")
            with open(self.path, 'r') as infile:
                return ujson.load(infile)
        return []

    def exists(self) -> bool:
        return self.file_exists(self.path)

    def clear(self):
        if self.exists():
            self.remove_file(self.path)
Exemplo n.º 15
0
class CliClient(Client):
    """
    Provides cli specific features.
    """
    logger = AppLogger("cli_client").get_logger()

    def __init__(self, db_manager, file_manager):
        super().__init__(db_manager)

        assert isinstance(file_manager, FileManager)
        self.__file_manager = file_manager

        self.task_table = TaskConsoleTable()
        self.snapshot_list_table = SnapshotListConsoleTable()
        self.snapshot_summary_table = SnapshotSummaryConsoleTable()
        self.variables_table = VariableConsoleTable()

    def display_tasks(self, task_list):
        return self.__print_tasks_table(task_list)

    def display_snapshots(self, snapshots: Snapshots, **kwargs):
        summary, snapshot_list = snapshots.get_snapshot()
        self.__print_snapshot_summary_table(summary)
        return self.__print_snapshot_list_table(snapshot_list,
                                                kwargs.get("page"))

    def list_labels(self):
        """
        Lists all labels contained in the tasks
        :return:
        """
        print("Labels: {}".format(self.get_unique_label_list()))

    def list_projects(self):
        """
        Lists all projects contained in the tasks
        :return:
        """
        print("Projects: {}".format(self.get_unique_project_list()))

    def list_default_variables(self):
        for key, value in self.get_variables_list():
            self.variables_table.add_row([key, value])
        self.variables_table.print()

    def __print_tasks_table(self, task_list):
        self.task_table.clear()
        for task in task_list:
            self.task_table.add_row(task)
        return self.task_table.print()

    def __print_snapshot_summary_table(self, summary: Snapshot):
        CliClient.logger.info("Snapshot summary:")
        self.snapshot_summary_table.clear()
        self.snapshot_summary_table.add_row(summary)
        return self.snapshot_summary_table.print()

    def __print_snapshot_list_table(self, snapshot_list: list,
                                    page_number: int):
        assert type(snapshot_list) is list
        assert type(page_number) is int

        if snapshot_list:
            self.snapshot_list_table.clear()
            pager = Pager(snapshot_list).assemble()
            page = pager.get_page(page_number)
            snapshots = pager.get_items(page)

            self.logger.info("Snapshot list:")
            for snapshot in snapshots:
                self.snapshot_list_table.add_row(snapshot)
            snapshot_list = self.snapshot_list_table.print()

            self.logger.info(
                f"Displayed {len(snapshots)} items - Page {page_number} of {pager.get_page_count()}"
            )

        return snapshot_list

    def export_tasks(self, task_list):
        self.__file_manager.save_tasks(task_list)

    def export_snapshots(self, snapshot_list: list):
        self.__file_manager.save_snapshots(snapshot_list)

    def import_tasks(self, csv_file_importer: CsvFileImporter, path: str):
        """
        Imports tasks from the Csv file.
        :param csv_file_importer: CsvFileImporter class
        :param path: path to csv file
        :return: None
        """
        assert isinstance(csv_file_importer, CsvFileImporter)
        assert type(path) is str

        start_datetime = datetime.now()
        self.logger.info(f"Starting import")
        obj_list = self.__file_manager.open_tasks(path)
        self.logger.info(f"Retrieved {len(obj_list)} tasks from file")
        task_list = csv_file_importer.convert(obj_list)
        sync_results = csv_file_importer.import_tasks(task_list)
        self.logger.info(f"Import summary: {sync_results.get_summary()}")

        self.logger.info(
            f"Import complete: Duration: {self.get_duration(start_datetime)}")

    def add_task(self, text: str, label: str, project: str,
                 date_expression: str) -> List[Task]:
        """
        Adds a task
        :param text: text string describing the task
        :param label: label for the task
        :param project: project name for the task
        :param date_expression: Must be one of [today, tomorrow, m-s, every *, month / day, etc]. For complete
        list see the expression_lists in handler objects in date_generator.py
        :return: list of Task
        """
        assert type(text) is str
        assert type(label) is str
        assert type(project) is str
        assert type(date_expression) is str
        try:
            task_list = self.tasks.add(text, label, project, date_expression)
            if len(task_list) == 1:
                self.logger.info(f"Added task #{task_list[0].index}")
            else:
                self.logger.info(f"Added {len(task_list)} tasks")
            return task_list
        except AttributeError as ex:
            self.logger.info(ex)

    def display_invalid_index_error(self, index: int):
        assert type(index) is int
        self.logger.info(f"Provided index {index} is invalid")

    def delete_tasks(self, index_tuple: tuple) -> list:
        assert type(index_tuple) is tuple
        results = list()
        for index in index_tuple:
            task = self.tasks.get_task_by_index(index)
            if task is not None:
                results.append(self.tasks.delete(task.unique_id))
            else:
                self.display_invalid_index_error(index)
        return results

    def complete_tasks(self, index_tuple: tuple) -> list:
        """
        Changes the completed status in the DueDate object
        :param index_tuple: int tuple
        :return: list
        """
        assert type(index_tuple) is tuple

        results = list()
        for index in index_tuple:
            task = self.tasks.get_task_by_index(index)
            if task is not None:
                if task.is_completed() is False:
                    results.append(self.tasks.complete(task.unique_id))
            else:
                self.display_invalid_index_error(index)
        return results

    def undelete_tasks(self, index_tuple: tuple) -> list:
        assert type(index_tuple) is tuple
        results = list()
        for index in index_tuple:
            task = self.tasks.get_task_by_index(index)
            if task is not None:
                results.append(self.tasks.undelete(task.unique_id))
            else:
                self.display_invalid_index_error(index)
        return results

    def reset_tasks(self, index_tuple: tuple) -> list:
        """
        Copies tasks from the past into the present.
        :param index_tuple: int tuple
        :return: list
        """
        assert type(index_tuple) is tuple

        results = list()
        for index in index_tuple:
            task = self.tasks.get_task_by_index(index)
            if task is not None:
                results.append(self.tasks.reset(task.unique_id))
            else:
                self.display_invalid_index_error(index)
        return results

    def list_all_tasks(self, display_all: bool = False) -> List[Task]:
        if display_all:
            task_list = self.tasks.get_object_list()
        else:
            task_list = [
                task for task in self.tasks.get_object_list()
                if not task.deleted
            ]
        return self.display_tasks(task_list)