Exemplo n.º 1
0
def freeze_tasks(
    repo: Repository,
    selector: TaskSelector,
) -> None:
    """Freeze a list of tasks based on a task filter."""
    tasks = _tasks_from_selector(repo, selector)
    for task in tasks:
        if type(task) == Task:
            child_task = task
            if child_task.parent_id is None:
                raise ValueError(
                    f"Task {child_task.id_}: {child_task.description} is not the child"
                    " of any recurrent task, so it can't be frozen")
            parent_task = repo.get(child_task.parent_id, [RecurrentTask])
        elif type(task) == RecurrentTask:
            parent_task = task
            try:
                child_task = repo.search(
                    {
                        "active": True,
                        "parent_id": task.id_
                    }, [Task])[0]
            except EntityNotFoundError as error:
                raise EntityNotFoundError(
                    f"The recurrent task {task.id_}: {task.description} has no active "
                    "children") from error
        parent_task.freeze()
        repo.add(parent_task)
        repo.delete(child_task)
        log.info(
            f"Frozen recurrent task {parent_task.id_}: {parent_task.description} and "
            f"deleted it's last child {child_task.id_}")
    repo.commit()
Exemplo n.º 2
0
    def test_thaw_task_by_id(
        self,
        runner: CliRunner,
        insert_frozen_parent_task_e2e: RecurrentTask,
        repo_e2e: Repository,
        caplog: LogCaptureFixture,
    ) -> None:
        """
        Given: A frozen recurrent task
        When: Thawed
        Then: The task is back active and it's next children is breed.
        """
        parent_task = insert_frozen_parent_task_e2e
        now = datetime.now()

        result = runner.invoke(cli, ["thaw", str(parent_task.id_)])

        parent_task = repo_e2e.get(parent_task.id_, [RecurrentTask])
        child_task = repo_e2e.search({"parent_id": parent_task.id_}, [Task])[0]
        assert result.exit_code == 0
        assert (
            "pydo.services",
            logging.INFO,
            f"Thawed task {parent_task.id_}: {parent_task.description}, and created "
            f"it's next child task with id {child_task.id_}",
        ) in caplog.record_tuples
        assert parent_task.state == TaskState.BACKLOG
        assert parent_task.active is True
        assert child_task.state == TaskState.BACKLOG
        assert child_task.active is True
        assert child_task.due is not None
        assert child_task.due > now
Exemplo n.º 3
0
    def test_repository_search_raises_error_if_searching_by_inexistent_value(
        self,
        repo: Repository,
        inserted_entities: List[Entity],
    ) -> None:
        """If no object has a value like the search criteria raise the desired error."""
        entity = inserted_entities[0]
        with pytest.warns(
            UserWarning, match="From 2022-06-10.*return an empty list"
        ), pytest.raises(
            EntityNotFoundError,
            match=(
                f"There are no entities of type {entity.model_name} in the "
                "repository that match the search filter {'id_': 'inexistent_value'}"
            ),
        ):

            repo.search({"id_": "inexistent_value"}, type(entity))  # act
Exemplo n.º 4
0
def print_task_report(
    repo: Repository,
    config: config.Config,
    report_name: str,
    task_selector: Optional[TaskSelector] = None,
) -> None:
    """Gather the common tasks required to print several tasks."""
    if task_selector is None:
        task_selector = TaskSelector()

    # Initialize the Report
    (
        columns,
        labels,
        default_task_filter,
        sort_criteria,
    ) = _get_task_report_configuration(config, report_name)
    colors = Colors(**config.data["themes"][config.get("theme")])

    report = Report(labels=labels, colors=colors)

    # Complete the task_selector with the report task_filter
    task_selector.task_filter.update(default_task_filter)

    # Change the sorting of the report with the values of the task selector
    if task_selector.sort != []:
        sort_criteria = task_selector.sort

    with suppress(KeyError):
        if task_selector.task_filter["type"] == "recurrent_task":
            task_selector.model = RecurrentTask
        task_selector.task_filter.pop("type")

    # Fill up the report with the selected tasks
    tasks = repo.search(task_selector.task_filter, [task_selector.model])
    for task in sort_tasks(tasks, sort_criteria):
        entity_line = []
        for attribute in columns:
            value = getattr(task, attribute)
            if isinstance(value, list):
                value = ", ".join(value)
            elif isinstance(value, datetime):
                value = _date2str(config, value)
            elif isinstance(value, Enum):
                value = value.value.title()
            elif value is None:
                value = ""

            entity_line.append(value)
        report.add(entity_line)

    # Clean up the report and print it
    report._remove_null_columns()
    report.print()
Exemplo n.º 5
0
    def test_repository_can_search_by_bool_property(
        self,
        repo: Repository,
    ) -> None:
        """Search should return the objects that have a bool property."""
        expected_entity = BoolEntity(name="Name", active=True)
        repo.add(expected_entity)
        repo.commit()

        result = repo.search({"active": True}, BoolEntity)

        assert result == [expected_entity]
Exemplo n.º 6
0
    def test_repository_can_search_by_property_specifying_a_list_of_types(
        self,
        repo: Repository,
        inserted_entities: List[Entity],
    ) -> None:
        """Search should return the objects that match the desired property."""
        entity_types: List[Type[Entity]] = [type(inserted_entities[0]), OtherEntity]
        expected_entity = inserted_entities[1]
        repo.models = entity_types  # type: ignore
        with pytest.warns(UserWarning, match="In 2022-06-10.*deprecated"):

            result = repo.search({"id_": expected_entity.id_}, entity_types)

        assert result == [expected_entity]
Exemplo n.º 7
0
    def test_repo_can_search_in_list_of_str_attribute(self, repo: Repository) -> None:
        """
        Given: A repository with an entity that contains an attribute with a list of str
        When: search is called with a regexp that  matches one of the list elements
        Then: the entity is returned
        """
        expected_entity = ListEntityFactory.build()
        repo.add(expected_entity)
        repo.commit()
        regexp = rf"{expected_entity.elements[0][:-1]}."

        result = repo.search({"elements": regexp}, ListEntity)

        assert result == [expected_entity]
Exemplo n.º 8
0
    def test_repository_search_returns_empty_list_if_none_found(
        self,
        repo: Repository,
        inserted_entities: List[Entity],
    ) -> None:
        """
        If no object has the property of the search criteria,
        return an empty list.
        """
        entity = inserted_entities[0]

        result = repo.search({"inexistent_field": "inexistent_value"},
                             type(entity))

        assert result == []
Exemplo n.º 9
0
    def test_search_doesnt_raise_exception_if_search_exception_false(
        self, repo: Repository
    ) -> None:
        """
        Given: A repository with search_exception False
        When: running search on a criteria that returns no results
        Then: an empty list is returned instead of an exception.

        See ADR 005 for more info.
        """
        repo = load_repository(search_exception=False)

        result = repo.search({"id_": "inexistent"}, Author)

        assert result == []
Exemplo n.º 10
0
    def test_repository_can_search_by_multiple_properties(
        self,
        repo: Repository,
        inserted_entities: List[Entity],
    ) -> None:
        """
        Given: a full repository.
        When: a search is performed by multiple properties.
        Then: the matching objects are returned.
        """
        entity = inserted_entities[1]

        result = repo.search({"state": entity.state, "name": entity.name}, type(entity))

        assert result == [entity]
Exemplo n.º 11
0
    def test_repository_can_search_regular_expression(
            self, repo: Repository, inserted_entities: List[Entity]) -> None:
        """
        Given: More than one entity is inserted in the repository.
        When: We search using a regular expression
        Then: The matching entity is found
        """
        expected_entities = [
            entity_ for entity_ in inserted_entities
            if entity_.name == inserted_entities[0].name
        ]
        regular_expression = rf"^{expected_entities[0].name}.*"

        result = repo.search({"name": regular_expression},
                             type(expected_entities[0]))

        assert result == expected_entities
Exemplo n.º 12
0
    def test_repository_can_search_by_property(
        self,
        repo: Repository,
        inserted_entities: List[Entity],
    ) -> None:
        """Search should return the objects that match the desired property.

        And the entity is saved to the cache.
        The defined_values of all entities are empty, otherwise the merge fails.
        """
        expected_entity = inserted_entities[1]

        result = repo.search({"id_": expected_entity.id_}, type(inserted_entities[1]))

        assert result == [expected_entity]
        assert repo.cache.get(expected_entity) == expected_entity
        assert result[0].defined_values == {}
Exemplo n.º 13
0
    def test_repo_can_search_entity_if_two_different_entities_match(
        self,
        repo: Repository,
    ) -> None:
        """
        Given: Two different entities with the same ID
        When: we search by a property equal in both entities and give only one model.
        Then: only the entity that matches the model is returned
        """
        author = Author(id_="author_id", name="common name")
        book = Book(id_=1, name="common name")
        repo.add(author)
        repo.add(book)
        repo.commit()

        result = repo.search({"name": "common name"}, Author)

        assert result == [author]
Exemplo n.º 14
0
    def test_thaw_task_by_id_accepts_state(
        self,
        runner: CliRunner,
        insert_frozen_parent_task_e2e: RecurrentTask,
        repo_e2e: Repository,
        caplog: LogCaptureFixture,
    ) -> None:
        """Test thawing accepts the thawing state of the tasks."""
        parent_task = insert_frozen_parent_task_e2e

        result = runner.invoke(
            cli, ["thaw", str(parent_task.id_), "-s", "todo"])

        parent_task = repo_e2e.get(parent_task.id_, [RecurrentTask])
        child_task = repo_e2e.search({"parent_id": parent_task.id_}, [Task])[0]
        assert result.exit_code == 0
        # T101: fixme found (T O D O). Lol, it's not a comment to fix something
        assert parent_task.state == TaskState.TODO  # noqa: T101
        assert child_task.state == TaskState.TODO  # noqa: T101
Exemplo n.º 15
0
    def test_repo_can_search_entity_if_two_different_entities_match_giving_both_models(
        self,
        repo: Repository,
    ) -> None:
        """
        Given: Two different entities with the same ID
        When: we search by a property equal in both entities and give both models.
        Then: both entities that matches the model are returned
        """
        author = Author(id_="author_id", name="common name")
        book = Book(id_=1, name="common name")
        repo.add(author)
        repo.add(book)
        repo.commit()
        with pytest.warns(UserWarning, match="In 2022-06-10.*deprecated"):

            result = repo.search({"name": "common name"}, [Author, Book])

        assert result == [book, author]
Exemplo n.º 16
0
def tags(repo: Repository) -> None:
    """Print the tags information."""
    report = Report(labels=["Name", "Open Tasks"])
    open_tasks = repo.search({"active": True}, [Task])

    # Gather tags
    tags: Dict[str, int] = {}
    for task in open_tasks:
        if len(task.tags) == 0:
            tags.setdefault("None", 0)
            tags["None"] += 1
        else:
            for tag in task.tags:
                tags.setdefault(tag, 0)
                tags[tag] += 1

    for tag in sorted(tags.keys()):
        report.add([tag, str(tags[tag])])

    report.print()
Exemplo n.º 17
0
def areas(repo: Repository) -> None:
    """Print the areas information."""
    report = Report(labels=["Name", "Open Tasks"])
    open_tasks = repo.search({"active": True}, [Task])

    # Gather areas
    areas: Dict[str, int] = {}
    for task in open_tasks:
        if task.area is None:
            area = "None"
        else:
            area = task.area

        areas.setdefault(area, 0)
        areas[area] += 1

    for area in sorted(areas.keys()):
        report.add([area, str(areas[area])])

    report.print()
Exemplo n.º 18
0
    def test_thaw_accepts_filter_of_tasks(
        self,
        runner: CliRunner,
        insert_frozen_parent_tasks_e2e: List[RecurrentTask],
        repo_e2e: Repository,
        caplog: LogCaptureFixture,
    ) -> None:
        """Test thawing accepts a filter of tasks."""
        result = runner.invoke(cli, ["thaw", "state:frozen"])

        assert result.exit_code == 0
        for parent_task in insert_frozen_parent_tasks_e2e:
            child_task = repo_e2e.search({"parent_id": parent_task.id_},
                                         [Task])[0]
            assert (
                "pydo.services",
                logging.INFO,
                f"Thawed task {parent_task.id_}: {parent_task.description}, and created"
                f" it's next child task with id {child_task.id_}",
            ) in caplog.record_tuples
Exemplo n.º 19
0
    def test_repository_search_by_regular_expression_is_case_insensitive(
            self, repo: Repository, inserted_entities: List[Entity]) -> None:
        """
        Given: More than one entity is inserted in the repository.
        When: We search using a regular expression with the wrong capitalization
        Then: The matching entity is found

        If you come to disable this functionality, make it configurable instead, being
        the default the search as case insensitive
        """
        expected_entities = [
            entity_ for entity_ in inserted_entities
            if entity_.name == inserted_entities[0].name
        ]
        regular_expression = rf"^{expected_entities[0].name.upper()}.*"

        result = repo.search({"name": regular_expression},
                             type(expected_entities[0]))

        assert result == expected_entities
Exemplo n.º 20
0
def thaw_tasks(repo: Repository,
               selector: TaskSelector,
               state: Optional[TaskState] = None) -> None:
    """Thaw a list of tasks based on a task filter."""
    selector.model = RecurrentTask
    selector.task_filter["state"] = TaskState.FROZEN

    if state is None:
        state = TaskState.BACKLOG

    tasks = _tasks_from_selector(repo, selector)

    if len(tasks) == 0:
        raise EntityNotFoundError(
            "No frozen tasks were found with that criteria")

    for task in tasks:
        if type(task) == RecurrentTask:
            task.thaw(state)
            repo.add(task)

            try:
                children = repo.search({
                    "parent_id": task.id_,
                    "active": False
                }, [Task])
            except EntityNotFoundError:
                children = []

            if len(children) == 0:
                last_child = None
            else:
                last_child = children[-1]

            child_task = repo.add(task.breed_children(last_child))

            log.info(
                f"Thawed task {task.id_}: {task.description}, and created it's next "
                f"child task with id {child_task.id_}")
    repo.commit()
Exemplo n.º 21
0
def _tasks_from_selector(repo: Repository,
                         selector: TaskSelector) -> List[TaskType]:
    """Return the tasks that match the criteria of the task selector."""
    tasks: List[TaskType] = [
        repo.get(task_id, [selector.model]) for task_id in selector.task_ids
    ]

    if selector.task_filter != {}:
        # Remove the tasks that don't meet the task_filter
        for task in tasks:
            # Check if the task_filter is not a subset of the properties of the task.
            # SIM205: Use 'selector.task_filter.items() > task.dict().items()' instead
            # No can't do, if we do, the subset checking doesn't work
            if not selector.task_filter.items() <= task.dict().items(
            ):  # noqa: SIM205
                tasks.remove(task)

        with suppress(EntityNotFoundError):
            tasks.extend(repo.search(selector.task_filter, [selector.model]))

    # Remove duplicates
    return list(set(tasks))