编写待办事项完成功能

举报
Yuchuan 发表于 2021/11/19 09:26:47 2021/11/19
【摘要】 构建用户友好的命令行界面 (CLI)应用程序是 Python 开发人员的一项基本技能。在 Python 生态系统中,您会找到多种用于创建此类应用程序的工具。argparse、Click和Typer等库是 Python 中这些工具的很好示例。在这里,您构建了一个 CLI 应用程序来使用 Python 和 Typer 管理待办事项列表。

第 6 步:编写待办事项完成功能

您将添加到待办事项应用程序的下一个功能是 Typer 命令,它允许您的用户将给定的待办事项设置为完整。这样,您的用户可以跟踪他们的进度并知道还剩下多少工作。

同样,您可以通过单击下面的链接并转到source_code_step_6/目录来下载本节的代码和所有资源,包括其他单元测试:

像往常一样,您将首先在Todoer. 在这种情况下,您需要一个接受待办事项 ID 并将相应待办事项标记为已完成的方法。返回到rptodo.py您的代码编辑器中并添加以下代码:

# rptodo/rptodo.py
# ...
from rptodo import DB_READ_ERROR, ID_ERROR
from rptodo.database import DatabaseHandler

# ...

class Todoer:
    # ...
    def set_done(self, todo_id: int) -> CurrentTodo:
        """Set a to-do as done."""
        read = self._db_handler.read_todos()
        if read.error:
            return CurrentTodo({}, read.error)
        try:
            todo = read.todo_list[todo_id - 1]
        except IndexError:
            return CurrentTodo({}, ID_ERROR)
        todo["Done"] = True
        write = self._db_handler.write_todos(read.todo_list)
        return CurrentTodo(todo, write.error)

您的新.set_done()方法可以完成所需的工作。就是这样:

  • 第 10 行定义了.set_done(). 该方法接受一个名为 的参数todo_id,该参数包含一个整数,表示您要标记为已完成的待办事项的 ID。当您使用list命令列出待办事项时,待办事项 ID 是与给定待办事项相关联的编号。由于您使用 Python列表来存储待办事项,因此您可以将此 ID 转换为从零开始的索引,并使用它从列表中检索所需的待办事项。

  • 第 12 行通过调用.read_todos()数据库处理程序读取所有待办事项。

  • 第 13 行检查读取过程中是否发生任何错误。如果是,则第 14 行返回一个命名元组,CurrentTodo,带有空的待办事项和错误。

  • 第 15 行开始了一个tryexcept语句来捕获无效的待办事项 ID,这些 ID 会转换为底层待办事项列表中的无效索引。如果IndexError发生,则第 18 行返回一个CurrentTodo带有空待办事项和相应错误代码的实例。

  • 第 19 行分配True"Done"目标待办事项字典中的键。这样,您就可以将待办事项设置为已完成。

  • 第 20 行通过调用.write_todos()数据库处理程序将更新写回数据库。

  • 第 21 行返回一个CurrentTodo带有目标待办事项的实例和一个指示操作如何进行的返回代码。

使用.set_done()到位后,您可以移动到cli.py并编写complete命令。这是所需的代码:

# rptodo/cli.py
# ...

@app.command(name="list")
def list_all() -> None:
    # ...

@app.command(name="complete")
def set_done(todo_id: int = typer.Argument(...)) -> None:
    """Complete a to-do by setting it as done using its TODO_ID."""
    todoer = get_todoer()
    todo, error = todoer.set_done(todo_id)
    if error:
        typer.secho(
            f'Completing to-do # "{todo_id}" failed with "{ERRORS[error]}"',
            fg=typer.colors.RED,
        )
        raise typer.Exit(1)
    else:
        typer.secho(
            f"""to-do # {todo_id} "{todo['Description']}" completed!""",
            fg=typer.colors.GREEN,
        )

def _version_callback(value: bool) -> None:
    # ...

一行一行地看一下这段代码是如何工作的:

  • 第 8 行和第 9 行定义set_done()为带有常用@app.command()装饰器的 Typer 命令。在这种情况下,您使用complete命令名称。该set_done()函数采用一个名为 的参数todo_id,该参数默认为 的一个实例typer.Argument。此实例将用作必需的命令行参数。

  • 第 11 行获取通常的Todoer实例。

  • 线12套待完成与特定todo_id的调用完成.set_done()todoer

  • 第 13 行检查在此过程中是否发生任何错误。如果是这样,那么第 14 到 18 行会打印一条适当的错误消息并以退出代码退出应用程序1。如果没有发生错误,则第 20 到 23 行以绿色字体打印成功消息。

就是这样!现在你可以complete试试你的新命令了。返回终端窗口,运行以下命令:

(venv) $ python -m rptodo list

to-do list:

ID.  | Priority  | Done  | Description
----------------------------------------
1    | (1)       | False | Get some milk.
2    | (3)       | False | Clean the house.
3    | (2)       | False | Wash the car.
----------------------------------------

(venv) $ python -m rptodo complete 1
to-do # 1 "Get some milk." completed!

(venv) $ python -m rptodo list

to-do list:

ID.  | Priority  | Done  | Description
----------------------------------------
1    | (1)       | True  | Get some milk.
2    | (3)       | False | Clean the house.
3    | (2)       | False | Wash the car.
----------------------------------------

首先,您列出所有待办事项以可视化与每个待办事项对应的 ID。然后您使用已完成complete的 ID 来设置待办事项1。当您再次列出待办事项时,您会看到第一个待办事项现在标记为True完成”列中。

关于complete命令和底层Todoer.set_done()方法需要注意的一个重要细节是待办事项 ID 不是固定值。如果您从列表中删除一个或多个待办事项,那么一些剩余的待办事项的 ID 将发生变化。说到删除待办事项,这就是您将在下一节中执行的操作。

第 7 步:编写删除待办事项功能的代码

从列表中删除待办事项是您可以添加到待办事项应用程序的另一个有用功能。在本部分中,您将使用 Python 向应用的 CLI 添加两个新的 Typer 命令。第一个命令是remove. 它将允许您的用户通过其 ID 删除待办事项。第二个命令clear将使用户能够从数据库中删除所有当前的待办事项。

您可以通过单击下面的链接并转到source_code_step_7/目录来下载本部分的代码、单元测试和其他附加资源:

实施removeCLI 命令

remove在应用程序的 CLI 中实现该命令,您首先需要.remove()Todoer. 此方法将提供使用待办事项 ID 从列表中删除单个待办事项的所有功能。请记住,您将待办事项 ID 设置为与特定待办事项相关联的整数。要显示待办事项 ID,请运行该list命令。

以下是您可以.remove()在其中编码的方法Todoer

# rptodo/rptodo.py
# ...

class Todoer:
    # ...
    def remove(self, todo_id: int) -> CurrentTodo:
        """Remove a to-do from the database using its id or index."""
        read = self._db_handler.read_todos()
        if read.error:
            return CurrentTodo({}, read.error)
        try:
            todo = read.todo_list.pop(todo_id - 1)
        except IndexError:
            return CurrentTodo({}, ID_ERROR)
        write = self._db_handler.write_todos(read.todo_list)
        return CurrentTodo(todo, write.error)

在这里,您的代码执行以下操作:

  • 第 6 行定义了.remove(). 此方法将待办事项 ID 作为参数,并从数据库中删除相应的待办事项。

  • 第 8 行通过调用.read_todos()数据库处理程序从数据库中读取待办事项列表。

  • 第 9 行检查读取过程中是否发生任何错误。如果是,则第 10 行返回一个命名元组 ,CurrentTodo,其中包含一个空的待办事项和相应的错误代码。

  • 第 11 行开始了一个tryexcept语句来捕获来自用户输入的任何无效 ID。

  • 第 12 行todo_id - 1从待办事项列表中删除索引处的待办事项。如果IndexError在此操作期间发生 ,则第 14 行返回一个CurrentTodo带有空待办事项和相应错误代码的实例。

  • 第 15行将更新后的待办事项列表写回数据库。

  • 第 16 行返回一个CurrentTodo元组,其中包含已删除的待办事项和指示操作成功的返回代码。

现在,在你完成编码.remove()Todoer,你可以去cli.py,并添加remove命令:

# rptodo/cli.py
# ...

@app.command()
def set_done(todo_id: int = typer.Argument(...)) -> None:
    # ...

@app.command()
def remove(
    todo_id: int = typer.Argument(...),
    force: bool = typer.Option(
        False,
        "--force",
        "-f",
        help="Force deletion without confirmation.",
    ),
) -> None:
    """Remove a to-do using its TODO_ID."""
    todoer = get_todoer()

    def _remove():
        todo, error = todoer.remove(todo_id)
        if error:
            typer.secho(
                f'Removing to-do # {todo_id} failed with "{ERRORS[error]}"',
                fg=typer.colors.RED,
            )
            raise typer.Exit(1)
        else:
            typer.secho(
                f"""to-do # {todo_id}: '{todo["Description"]}' was removed""",
                fg=typer.colors.GREEN,
            )

    if force:
        _remove()
    else:
        todo_list = todoer.get_todo_list()
        try:
            todo = todo_list[todo_id - 1]
        except IndexError:
            typer.secho("Invalid TODO_ID", fg=typer.colors.RED)
            raise typer.Exit(1)
        delete = typer.confirm(
            f"Delete to-do # {todo_id}: {todo['Description']}?"
        )
        if delete:
            _remove()
        else:
            typer.echo("Operation canceled")

def _version_callback(value: bool) -> None:
    # ...

哇!这是很多代码。这是它的工作原理:

  • 第 8 行和第 9 行定义remove()为 Typer CLI 命令。

  • 第 10 行定义todo_id为 type 的参数int。在这种情况下,todo_id是 的必需实例typer.Argument

  • 第 11 行定义forceremove命令的选项。这是一个布尔选项,允许您无需确认即可删除待办事项。此选项默认为False(第 12 行),其标志为--force-f(第 13 行和第 14 行)。

  • 第 15 行定义了该force选项的帮助消息。

  • 第 19 行创建所需的Todoer实例。

  • 第 21 到 33 行定义了一个名为的内部函数_remove()。它是一个辅助函数,允许您重用删除功能。该函数使用其 ID 删除待办事项。要做到这一点,它调用.remove()todoer

  • 第 35 行检查 的值force。一个True值意味着用户想要在没有确认的情况下删除待办事项。在这种情况下,第 36 行调用_remove()运行删除操作。

  • 第 37 行开始了一个else运行 if forceis的子句False

  • 第 38行从数据库中获取整个待办事项列表。

  • 第 39 到 43 行定义了一个tryexcept语句,用于从列表中检索所需的待办事项。如果IndexError发生,则第 42 行打印一条错误消息,第 43 行退出应用程序。

  • 第 44 到 46 行调用 Typerconfirm()并将结果存储在delete. 此功能提供了另一种要求确认的方式。它允许您使用动态创建的确认提示,如第 45 行中的提示。

  • 第 47 行检查是否deleteTrue,在这种情况下,第 48 行调用_remove()。否则,第 50 行表示操作已取消。

您可以remove通过在命令行上运行以下命令来试用该命令:

(venv) $ python -m rptodo list

to-do list:

ID.  | Priority  | Done  | Description
----------------------------------------
1    | (1)       | True  | Get some milk.
2    | (3)       | False | Clean the house.
3    | (2)       | False | Wash the car.
----------------------------------------

(venv) $ python -m rptodo remove 1
Delete to-do # 1: Get some milk.? [y/N]:
Operation canceled

(venv) $ python -m rptodo remove 1
Delete to-do # 1: Get some milk.? [y/N]: y
to-do # 1: 'Get some milk.' was removed

(venv) $ python -m rptodo list

to-do list:

ID.  | Priority  | Done  | Description
----------------------------------------
1    | (3)       | False | Clean the house.
2    | (2)       | False | Wash the car.
----------------------------------------

在这组命令中,您首先使用该list命令列出所有当前的待办事项。然后您尝试使用 ID 号删除待办事项1。这会向您显示是 ( y) 或否 ( N) 确认提示。如果按Enter,应用程序将运行默认选项N,并取消删除操作。

注意:如果您使用的是大于 0.3.2 的 Typer 版本,那么上例中的确认提示的外观和行为可能会有所不同。

例如,在 macOS 上,确认提示没有默认答案:

$ # Typer version 0.4.0 on macOS
$ python -m rptodo remove 1
Delete to-do # 1: Get some milk.? [y/n]:
Error: invalid input

如果这就是您的情况,那么您需要在命令行中明确提供答案,然后按Enter

在第三个命令中,您明确提供了一个y答案,因此应用程序会删除待办事项 ID 号1。如果您再次列出所有待办事项,则会看到该待办事项"Get some milk."不再在列表中。作为实验,继续尝试使用--force-f选项或尝试删除不在列表中的待办事项。

实施clearCLI 命令

在本节中,您将实现该clear命令。此命令将允许您的用户从数据库中删除所有待办事项。clear命令下面是.remove_all()方法 from Todoer,它提供后端功能。

返回rptodo.py.remove_all()在末尾添加Todoer

# rptodo/rptodo.py
# ...

class Todoer:
    # ...
    def remove_all(self) -> CurrentTodo:
        """Remove all to-dos from the database."""
        write = self._db_handler.write_todos([])
        return CurrentTodo({}, write.error)

在里面.remove_all(),您通过用空列表替换当前的待办事项列表来从数据库中删除所有的待办事项。为了一致性,该方法返回一个CurrentTodo带有空字典和适当返回或错误代码的元组。

现在您可以clear在应用程序的 CLI 中实现该命令:

# rptodo/cli.py
# ...

@app.command()
def remove(
    # ...

@app.command(name="clear")
def remove_all(
    force: bool = typer.Option(
        ...,
        prompt="Delete all to-dos?",
        help="Force deletion without confirmation.",
    ),
) -> None:
    """Remove all to-dos."""
    todoer = get_todoer()
    if force:
        error = todoer.remove_all().error
        if error:
            typer.secho(
                f'Removing to-dos failed with "{ERRORS[error]}"',
                fg=typer.colors.RED,
            )
            raise typer.Exit(1)
        else:
            typer.secho("All to-dos were removed", fg=typer.colors.GREEN)
    else:
        typer.echo("Operation canceled")

def _version_callback(value: bool) -> None:
    # ...

下面是这段代码的工作原理:

  • 第 8 行和第 9 行remove_all()使用@app.command()装饰器clear作为命令名称定义为 Typer 命令。

  • 第 10 到 14 行定义force为 Typer Option。它是布尔类型的必需选项。该prompt参数要求用户输入一个适当的值 to force,可以是yn

  • 第 13 行提供了该force选项的帮助消息。

  • 第 17 行获取通常的Todoer实例。

  • 第 18 行检查是否forceTrue。如果是,则if代码块使用.remove_all(). 如果在此过程中出现问题,应用程序会打印一条错误消息并退出(第 21 到 25 行)。否则,它会在第 27 行打印一条成功消息。

  • 如果用户通过向 提供 false 值(指示no)来取消删除操作,则第29行将运行force

clear尝试这个新命令,请继续并在您的终端上运行以下命令:

(venv) $ python -m rptodo clear
Delete all to-dos? [y/N]:
Operation canceled

(venv) $ python -m rptodo clear
Delete all to-dos? [y/N]: y
All to-dos were removed

(venv) $ python -m rptodo list
There are no tasks in the to-do list yet

在第一个示例中,您运行clear. 按 后Enter,您会收到一个提示,询问是 ( y) 或否 ( N) 确认。大写N表示no是默认答案,因此如果您按Enter,您将有效地取消clear操作。

在第二个示例中,您clear再次运行。这一次,您明确输入y作为提示的答案。此答案使应用程序从数据库中删除整个待办事项列表。当您运行该list命令时,您会收到一条消息,表明当前待办事项列表中没有任何任务。

就是这样!现在您拥有了一个使用 Python 和 Typer 构建的功能性 CLI 待办事项应用程序。您的应用程序提供命令和选项来创建新的待办事项、列出所有待办事项、管理待办事项的完成情况以及根据需要删除待办事项。这不是很酷吗?

结论

构建用户友好的命令行界面 (CLI)应用程序是 Python 开发人员的一项基本技能。在 Python 生态系统中,您会找到多种用于创建此类应用程序的工具。argparseClickTyper等库是 Python 中这些工具的很好示例。在这里,您构建了一个 CLI 应用程序来使用 Python 和 Typer 管理待办事项列表。

在本教程中,您学习了如何:

  • 使用 Python 和Typer构建待办事项应用程序
  • 使用 Typer 向待办事项应用程序添加命令参数选项
  • 在 Python 中使用TyperCliRunnerpytest测试您的待办事项应用程序

你也练过一些额外的技能,比如有工作JSON文件使用Python的json模块和管理配置文件与Python的configparser模块。现在您已准备好构建命令行应用程序。

您可以通过单击下面的链接并转到source_code_final/目录来下载该项目的整个代码和所有资源:

下一步

在本教程中,您已经使用 Python 和 Typer 为命令行构建了一个功能齐全的待办事项应用程序。尽管该应用程序仅提供了最少的功能集,但它是您继续添加功能并在此过程中不断学习的良好起点。这将帮助您将 Python 技能提升到一个新的水平。

以下是您可以实施以继续扩展您的待办事项应用程序的一些想法:

  • 添加对日期和截止日期的支持:您可以使用该datetime模块来完成这项工作。此功能将允许用户更好地控制他们的任务。

  • 编写更多单元测试:您可以使用 pytest 为您的代码编写更多测试。这将增加代码覆盖率并帮助您提高测试技能。您可能会在此过程中发现一些错误。如果是这样,请继续并将它们发布在评论中。

  • 打包应用程序并将其发布到 PyPI:您可以使用Poetry或其他类似工具打包您的待办事项应用程序并将其发布到 PyPI

这些只是一些想法。接受挑战,在这个项目的基础上构建一些很酷的东西!在这个过程中你会学到很多东西。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。