Bây giờ chúng ta đã vượt xa dự án trường học oẳn tù tì chủ yếu - hãy đi sâu vào dự án đó. Chúng ta sẽ đạt được gì thông qua hướng dẫn này? Trong , chúng tôi đã tạo thành công logic nghiệp vụ để tương tác với Trello SDK. Cách tạo chương trình Python CLI để quản lý bảng Trello (Phần 1) Dưới đây là bản tóm tắt nhanh về kiến trúc của chương trình CLI của chúng tôi: Trong hướng dẫn này, chúng ta sẽ xem cách chuyển đổi dự án của mình thành chương trình CLI, tập trung vào các yêu cầu chức năng và phi chức năng. Mặt khác, chúng ta cũng sẽ học cách phân phối chương trình của mình dưới dạng gói trên . PyPI Bắt đầu nào Cấu trúc thư mục Trước đây, chúng tôi đã thiết lập được bộ khung để lưu trữ mô-đun của mình. Lần này, chúng tôi muốn triển khai một thư mục với các mô-đun cho các chức năng khác nhau, cụ thể là: trelloservice cli cấu hình truy cập danh sách Ý tưởng là, đối với mỗi nhóm lệnh, các lệnh của nó sẽ được lưu trữ trong mô-đun riêng. Đối với lệnh , chúng tôi sẽ lưu trữ nó trong tệp CLI chính vì nó không thuộc bất kỳ nhóm lệnh nào. list Mặt khác, chúng ta hãy xem xét việc dọn dẹp cấu trúc thư mục của chúng ta. Cụ thể hơn, chúng ta nên bắt đầu tính toán khả năng mở rộng của phần mềm bằng cách đảm bảo rằng các thư mục không bị lộn xộn. Đây là một gợi ý về cấu trúc thư mục của chúng tôi: trellocli/ __init__.py __main__.py trelloservice.py shared/ models.py custom_exceptions.py cli/ cli.py cli_config.py cli_create.py tests/ test_cli.py test_trelloservice.py assets/ images/ README.md pyproject.toml .env .gitignore Chú ý có thư mục như thế nào? Điều này sẽ được sử dụng để lưu trữ các nội dung liên quan cho của chúng tôi trong khi có một thư mục mới được triển khai trong , chúng tôi sẽ sử dụng nó để lưu trữ các mô-đun sẽ được sử dụng trên phần mềm. assets README shared trellocli Cài đặt Hãy bắt đầu bằng cách sửa đổi tệp điểm nhập của chúng tôi, . Nhìn vào bản thân quá trình nhập, vì chúng tôi đã quyết định lưu trữ các mô-đun liên quan trong các thư mục con của riêng chúng nên chúng tôi sẽ phải điều chỉnh những thay đổi đó. Mặt khác, chúng tôi cũng giả định rằng mô-đun CLI chính, , có một phiên bản mà chúng tôi có thể chạy. __main__.py cli.py app # trellocli/__main__.py # module imports from trellocli import __app_name__ from trellocli.cli import cli from trellocli.trelloservice import TrelloService # dependencies imports # misc imports def main(): cli.app(prog_name=__app_name__) if __name__ == "__main__": main() Chuyển nhanh tới tệp của chúng tôi; chúng tôi sẽ lưu trữ phiên bản của chúng tôi ở đây. Ý tưởng là khởi tạo một đối tượng để chia sẻ trên phần mềm. cli.py app Typer # trellocli/cli/cli.py # module imports # dependencies imports from typer import Typer # misc imports # singleton instances app = Typer() Tiếp tục với khái niệm này, hãy sửa đổi để chỉ định các tập lệnh dòng lệnh của chúng tôi. Tại đây, chúng tôi sẽ cung cấp tên cho gói của mình và xác định điểm vào. pyproject.toml # pyproject.toml [project.scripts] trellocli = "trellocli.__main__:main" Dựa trên mẫu ở trên, chúng tôi đã xác định là tên gói và hàm trong tập lệnh , được lưu trữ trong mô-đun sẽ được thực thi trong thời gian chạy. trellocli main __main__ trellocli Bây giờ phần CLI của phần mềm đã được thiết lập, hãy sửa đổi mô-đun để phục vụ chương trình CLI tốt hơn. Như bạn nhớ lại, mô-đun của chúng tôi được thiết lập để yêu cầu ủy quyền của người dùng một cách đệ quy cho đến khi được phê duyệt. Chúng tôi sẽ sửa đổi điều này để chương trình sẽ thoát nếu không được cấp quyền và khuyến khích người dùng chạy lệnh . Điều này sẽ đảm bảo rằng chương trình của chúng tôi rõ ràng hơn và mang tính mô tả hơn về mặt hướng dẫn. trelloservice trelloservice config access Để diễn đạt điều này thành lời, chúng tôi sẽ sửa đổi các chức năng này: __init__ __load_oauth_token_env_var authorize is_authorized Bắt đầu với hàm , chúng ta sẽ khởi tạo một ứng dụng khách trống thay vì xử lý việc thiết lập ứng dụng khách tại đây. __init__ # trellocli/trelloservice.py class TrelloService: def __init__(self) -> None: self.__client = None 💡Bạn có thể sửa đổi hàm của chúng tôi để nó không nhắc đệ quy về ủy quyền của người dùng không? Gợi ý: Hàm đệ quy là hàm gọi chính nó. Góc thử thách __load_oauth_token_env_var Chuyển sang các hàm trợ giúp và , ý tưởng là sẽ thực hiện logic nghiệp vụ của việc thiết lập ứng dụng khách bằng cách sử dụng hàm trong khi hàm chỉ trả về giá trị boolean cho biết liệu ủy quyền có được cấp hay không. authorize is_authorized authorize __load_oauth_token_env_var is_authorized # trellocli/trelloservice.py class TrelloService: def authorize(self) -> AuthorizeResponse: """Method to authorize program to user's trello account Returns AuthorizeResponse: success / error """ self.__load_oauth_token_env_var() load_dotenv() if not os.getenv("TRELLO_OAUTH_TOKEN"): return AuthorizeResponse(status_code=TRELLO_AUTHORIZATION_ERROR) else: self.__client = TrelloClient( api_key=os.getenv("TRELLO_API_KEY"), api_secret=os.getenv("TRELLO_API_SECRET"), token=os.getenv("TRELLO_OAUTH_TOKEN") ) return AuthorizeResponse(status_code=SUCCESS) def is_authorized(self) -> bool: """Method to check authorization to user's trello account Returns bool: authorization to user's account """ if not self.__client: return False else: return True Hiểu rằng sự khác biệt giữa và là là một dùng để lưu trữ mã thông báo ủy quyền dưới dạng biến môi trường trong khi là chức năng đối mặt công khai, nó cố gắng truy xuất tất cả thông tin xác thực cần thiết và khởi tạo Ứng dụng khách Trello. __load_oauth_token_env_var authorize __load_oauth_token_env_var hàm đối diện nội bộ authorize 💡Lưu ý cách hàm của chúng tôi trả về loại dữ liệu . Bạn có thể triển khai mô hình có thuộc tính không? Tham khảo (Gợi ý: Hãy xem cách chúng tôi tạo Mô hình) Góc thử thách authorize AuthorizeResponse status_code Phần 1 của Cách tạo chương trình Python CLI để quản lý bảng Trello Cuối cùng, hãy khởi tạo một đối tượng đơn lẻ ở cuối mô-đun. Vui lòng tham khảo bản vá này để xem mã đầy đủ trông như thế nào: TrelloService trello-cli-kit # trellocli/trelloservice.py trellojob = TrelloService() Cuối cùng, chúng tôi muốn khởi tạo một số ngoại lệ tùy chỉnh để chia sẻ trong chương trình. Điều này khác với được xác định trong trình khởi tạo của chúng tôi vì các ngoại lệ này là các lớp con từ và hoạt động như các ngoại lệ điển hình do người dùng xác định, trong khi phục vụ nhiều hơn dưới dạng các giá trị không đổi bắt đầu từ 0. ERRORS BaseException ERRORS Hãy giữ các ngoại lệ của chúng ta ở mức tối thiểu và thực hiện một số trường hợp sử dụng phổ biến, đáng chú ý nhất là: Lỗi đọc: Xảy ra khi có lỗi đọc từ Trello Lỗi ghi: Xảy ra khi có lỗi ghi vào Trello Lỗi ủy quyền: Xảy ra khi ủy quyền không được cấp cho Trello Lỗi đầu vào của người dùng không hợp lệ: Xảy ra khi đầu vào CLI của người dùng không được nhận dạng # trellocli/shared/custom_exceptions.py class TrelloReadError(BaseException): pass class TrelloWriteError(BaseException): pass class TrelloAuthorizationError(BaseException): pass class InvalidUserInputError(BaseException): pass Kiểm tra đơn vị Như đã đề cập trong Phần I, chúng ta sẽ không trình bày rộng rãi về Unit Test trong hướng dẫn này, vì vậy hãy chỉ làm việc với các phần tử cần thiết: Kiểm tra để cấu hình quyền truy cập Test cấu hình bảng trello Test tạo thẻ trello mới Kiểm tra hiển thị chi tiết bảng trello Test hiển thị chi tiết bảng trello (xem chi tiết) Ý tưởng là mô phỏng một trình thông dịch dòng lệnh, giống như một để kiểm tra kết quả mong đợi. Điều tuyệt vời ở mô-đun là nó đi kèm với đối tượng riêng. Để chạy thử nghiệm, chúng tôi sẽ ghép nối nó với mô-đun . Để biết thêm thông tin, hãy xem . shell Typer runner pytest tài liệu chính thức của Typer Chúng ta hãy cùng nhau thực hiện bài kiểm tra đầu tiên, tức là định cấu hình quyền truy cập. Hiểu rằng chúng tôi đang kiểm tra xem hàm có thực thi đúng cách hay không. Để làm như vậy, chúng tôi sẽ kiểm tra phản hồi của hệ thống và liệu mã thoát có hay còn gọi là 0. Đây là một bài viết hay của RedHat về . success mã thoát là gì và cách hệ thống sử dụng chúng để giao tiếp với các quy trình # trellocli/tests/test_cli.py # module imports from trellocli.cli.cli import app # dependencies imports from typer.testing import CliRunner # misc imports runner = CliRunner() def test_config_access(): res = runner.invoke(app, ["config", "access"]) assert result.exit_code == 0 assert "Go to the following link in your browser:" in result.stdout 💡Bây giờ bạn đã nắm được ý chính, bạn có thể tự mình thực hiện các trường hợp thử nghiệm khác không? (Gợi ý: bạn cũng nên xem xét việc kiểm tra các trường hợp lỗi) Góc thử thách Logic kinh doanh Mô-đun CLI chính Hãy hiểu rằng đây sẽ là mô-đun chính của chúng tôi - đối với tất cả các nhóm lệnh (cấu hình, tạo), logic nghiệp vụ của chúng sẽ được lưu trữ trong tệp riêng để dễ đọc hơn. cli Trong mô-đun này, chúng tôi sẽ lưu trữ lệnh của mình. Đi sâu hơn vào lệnh, chúng tôi biết rằng chúng tôi muốn triển khai các tùy chọn sau: list board_name: bắt buộc nếu chưa được đặt trước đó config board chi tiết: hiển thị ở chế độ xem chi tiết Bắt đầu với tùy chọn bắt buộc board_name, có một số cách để đạt được điều này, một trong số đó là sử dụng hàm gọi lại (Để biết thêm thông tin, đây là ) hoặc đơn giản là sử dụng biến môi trường mặc định. Tuy nhiên, đối với trường hợp sử dụng của chúng ta, hãy đơn giản hóa vấn đề bằng cách đưa ra ngoại lệ tùy chỉnh nếu các điều kiện không được đáp ứng. tài liệu chính thức InvalidUserInputError Để xây dựng lệnh, hãy bắt đầu bằng việc xác định các tùy chọn. Trong Typer, như đã đề cập trong của họ, các thành phần chính để xác định một tùy chọn sẽ là: tài liệu chính thức Loại dữ liệu Văn bản trợ giúp Giá trị mặc định Ví dụ: để tạo tùy chọn với các điều kiện sau: detailed Kiểu dữ liệu: bool Văn bản trợ giúp: “Bật chế độ xem chi tiết” Giá trị mặc định: Không có Mã của chúng tôi sẽ trông như thế này: detailed: Annotated[bool, typer.Option(help=”Enable detailed view)] = None Nhìn chung, để xác định lệnh với các tùy chọn cần thiết, chúng ta sẽ coi là một Hàm Python và các tùy chọn của nó là các tham số bắt buộc. list list # trellocli/cli/cli.py @app.command() def list( detailed: Annotated[bool, Option(help="Enable detailed view")] = None, board_name: Annotated[str, Option(help="Trello board to search")] = "" ) -> None: pass Lưu ý rằng chúng tôi đang thêm lệnh vào phiên bản được khởi tạo ở đầu tệp. Vui lòng điều hướng qua để sửa đổi Tùy chọn theo ý thích của bạn. app cơ sở mã Typer chính thức Đối với quy trình làm việc của lệnh, chúng tôi sẽ thực hiện một số việc như thế này: Kiểm tra ủy quyền Định cấu hình để sử dụng bảng thích hợp (kiểm tra xem tùy chọn đã được cung cấp chưa) board_name Thiết lập bảng Trello để đọc từ Truy xuất dữ liệu thẻ Trello thích hợp và phân loại dựa trên danh sách Trello Hiển thị dữ liệu (kiểm tra xem tùy chọn đã được chọn chưa) detailed Một số điều cần lưu ý… Chúng tôi muốn đưa ra Ngoại lệ khi trellojob tạo ra mã trạng thái khác với . Bằng cách sử dụng các khối , chúng tôi có thể ngăn chương trình của mình gặp sự cố nghiêm trọng. SUCCESS try-catch Khi định cấu hình bảng thích hợp để sử dụng, chúng tôi sẽ cố gắng thiết lập bảng Trello để sử dụng dựa trên đã truy xuất. Vì vậy, chúng tôi muốn đề cập đến các trường hợp sử dụng sau board_id Truy xuất nếu được cung cấp rõ ràng bằng cách kiểm tra sự trùng khớp bằng hàm trong trellojob board_id board_name get_all_boards Truy xuất được lưu dưới dạng biến môi trường nếu tùy chọn không được sử dụng board_id board_name Dữ liệu chúng tôi sẽ hiển thị sẽ được định dạng bằng chức năng từ gói . Để biết thêm thông tin về , vui lòng tham khảo của họ Table rich rich tài liệu chính thức Chi tiết: Hiển thị tóm tắt về số lượng danh sách Trello, số lượng thẻ và nhãn được xác định. Đối với mỗi danh sách Trello, hiển thị tất cả các thẻ và tên, mô tả và nhãn liên quan tương ứng của chúng Không chi tiết: Hiển thị tóm tắt số lượng danh sách Trello, số lượng thẻ và nhãn được xác định Đặt mọi thứ lại với nhau, chúng ta có được một cái gì đó như sau. Tuyên bố từ chối trách nhiệm: có thể có một số chức năng bị thiếu trong mà chúng tôi chưa triển khai. Vui lòng tham khảo bản vá này nếu bạn cần trợ giúp triển khai chúng: TrelloService trello-cli-kit # trellocli/cli/cli.py # module imports from trellocli.trelloservice import trellojob from trellocli.cli import cli_config, cli_create from trellocli.misc.custom_exceptions import * from trellocli import SUCCESS # dependencies imports from typer import Typer, Option from rich import print from rich.console import Console from rich.table import Table from dotenv import load_dotenv # misc imports from typing_extensions import Annotated import os # singleton instances app = Typer() console = Console() # init command groups app.add_typer(cli_config.app, name="config", help="COMMAND GROUP to initialize configurations") app.add_typer(cli_create.app, name="create", help="COMMAND GROUP to create new Trello elements") @app.command() def list( detailed: Annotated[ bool, Option(help="Enable detailed view") ] = None, board_name: Annotated[str, Option()] = "" ) -> None: """COMMAND to list board details in a simplified (default)/detailed view OPTIONS detailed (bool): request for detailed view board_name (str): board to use """ try: # check authorization res_is_authorized = trellojob.is_authorized() if not res_is_authorized: print("[bold red]Error![/] Authorization hasn't been granted. Try running `trellocli config access`") raise AuthorizationError # if board_name OPTION was given, attempt to retrieve board id using the name # else attempt to retrieve board id stored as an env var board_id = None if not board_name: load_dotenv() if not os.getenv("TRELLO_BOARD_ID"): print("[bold red]Error![/] A trello board hasn't been configured to use. Try running `trellocli config board`") raise InvalidUserInputError board_id = os.getenv("TRELLO_BOARD_ID") else: res_get_all_boards = trellojob.get_all_boards() if res_get_all_boards.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving boards from trello") raise TrelloReadError boards_list = {board.name: board.id for board in res_get_all_boards.res} # retrieve all board id(s) and find matching board name if board_name not in boards_list: print("[bold red]Error![/] An invalid trello board name was provided. Try running `trellocli config board`") raise InvalidUserInputError board_id = boards_list[board_name] # configure board to use res_get_board = trellojob.get_board(board_id=board_id) if res_get_board.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when configuring the trello board to use") raise TrelloReadError board = res_get_board.res # retrieve data (labels, trellolists) from board res_get_all_labels = trellojob.get_all_labels(board=board) if res_get_all_labels.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving data from board") raise TrelloReadError labels_list = res_get_all_labels.res res_get_all_lists = trellojob.get_all_lists(board=board) if res_get_all_lists.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving data from board") raise TrelloReadError trellolists_list = res_get_all_lists.res # store data on cards for each trellolist trellolists_dict = {trellolist: [] for trellolist in trellolists_list} for trellolist in trellolists_list: res_get_all_cards = trellojob.get_all_cards(trellolist=trellolist) if res_get_all_cards.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving cards from trellolist") raise TrelloReadError cards_list = res_get_all_cards.res trellolists_dict[trellolist] = cards_list # display data (lists count, cards count, labels) # if is_detailed OPTION is selected, display data (name, description, labels) for each card in each trellolist print() table = Table(title="Board: "+board.name, title_justify="left", show_header=False) table.add_row("[bold]Lists count[/]", str(len(trellolists_list))) table.add_row("[bold]Cards count[/]", str(sum([len(cards_list) for cards_list in trellolists_dict.values()]))) table.add_row("[bold]Labels[/]", ", ".join([label.name for label in labels_list if label.name])) console.print(table) if detailed: for trellolist, cards_list in trellolists_dict.items(): table = Table("Name", "Desc", "Labels", title="List: "+trellolist.name, title_justify="left") for card in cards_list: table.add_row(card.name, card.description, ", ".join([label.name for label in card.labels if label.name])) console.print(table) print() except (AuthorizationError, InvalidUserInputError, TrelloReadError): print("Program exited...") Để xem phần mềm của chúng tôi hoạt động, chỉ cần chạy trong thiết bị đầu cuối. Theo mặc định, mô-đun Typer sẽ tự điền đầu ra cho lệnh . Và hãy chú ý cách chúng ta có thể gọi làm tên gói - hãy nhớ lại cách định nghĩa trước đây trong của chúng ta? python -m trellocli --help --help trellocli pyproject.toml Hãy tua nhanh một chút và khởi tạo các nhóm lệnh và . Để làm như vậy, chúng ta chỉ cần sử dụng hàm trên đối tượng của mình. Ý tưởng là nhóm lệnh sẽ có đối tượng riêng và chúng ta sẽ chỉ thêm đối tượng đó vào chính trong , cùng với tên của nhóm lệnh và văn bản trợ giúp. Nó sẽ trông giống như thế này create config add_typer app app app cli.py # trellocli/cli/cli.py app.add_typer(cli_config.app, name="config", help="COMMAND GROUP to initialize configurations") 💡Bạn có thể tự nhập nhóm lệnh không? Vui lòng tham khảo bản vá này để được trợ giúp: Góc thử thách create trello-cli-kit Lệnh phụ Để thiết lập một nhóm lệnh cho , chúng tôi sẽ lưu trữ các lệnh tương ứng trong mô-đun riêng của nó. Thiết lập tương tự như với nhu cầu khởi tạo đối tượng Typer. Đối với các lệnh, chúng tôi cũng muốn tuân thủ nhu cầu sử dụng các ngoại lệ tùy chỉnh. Một chủ đề bổ sung mà chúng tôi muốn đề cập đến là khi người dùng nhấn , hay nói cách khác, làm gián đoạn quá trình. Lý do tại sao chúng tôi không đề cập đến điều này trong lệnh của mình là vì sự khác biệt ở đây là nhóm lệnh bao gồm các lệnh tương tác. Sự khác biệt chính giữa các lệnh tương tác là chúng yêu cầu sự tương tác liên tục của người dùng. Tất nhiên, nói rằng lệnh trực tiếp của chúng tôi mất nhiều thời gian để thực thi. Đây cũng là cách tốt nhất để xử lý các ngắt quãng có thể xảy ra với bàn phím. create cli.py Ctrl + C list config Bắt đầu với lệnh , cuối cùng chúng ta sẽ sử dụng hàm như được tạo trong . Vì hàm tự xử lý cấu hình nên chúng tôi sẽ chỉ phải xác minh việc thực thi quy trình. access authorize TrelloService authorize # trellocli/cli/cli_config.py @app.command() def access() -> None: """COMMAND to configure authorization for program to access user's Trello account""" try: # check authorization res_authorize = trellojob.authorize() if res_authorize.status_code != SUCCESS: print("[bold red]Error![/] Authorization hasn't been granted. Try running `trellocli config access`") raise AuthorizationError except KeyboardInterrupt: print("[yellow]Keyboard Interrupt.[/] Program exited...") except AuthorizationError: print("Program exited...") Đối với lệnh , chúng tôi sẽ sử dụng nhiều mô-đun khác nhau để mang lại trải nghiệm tốt cho người dùng, bao gồm để hiển thị GUI đầu cuối cho tương tác của người dùng. Ý tưởng chính là như sau: board Menu thiết bị đầu cuối đơn giản Kiểm tra ủy quyền Truy xuất tất cả bảng Trello từ tài khoản của người dùng Hiển thị menu đầu cuối lựa chọn duy nhất của bảng Trello Đặt ID bảng Trello đã chọn làm biến môi trường # trellocli/cli/cli_config.py @app.command() def board() -> None: """COMMAND to initialize Trello board""" try: # check authorization res_is_authorized = trellojob.is_authorized() if not res_is_authorized: print("[bold red]Error![/] Authorization hasn't been granted. Try running `trellocli config access`") raise AuthorizationError # retrieve all boards res_get_all_boards = trellojob.get_all_boards() if res_get_all_boards.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving trello boards") raise TrelloReadError boards_list = {board.name: board.id for board in res_get_all_boards.res} # for easy access to board id when given board name # display menu to select board boards_menu = TerminalMenu( boards_list.keys(), title="Select board:", raise_error_on_interrupt=True ) boards_menu.show() selected_board = boards_menu.chosen_menu_entry # set board ID as env var dotenv_path = find_dotenv() set_key( dotenv_path=dotenv_path, key_to_set="TRELLO_BOARD_ID", value_to_set=boards_list[selected_board] ) except KeyboardInterrupt: print("[yellow]Keyboard Interrupt.[/] Program exited...") except (AuthorizationError, TrelloReadError): print("Program exited...") Cuối cùng, chúng ta sẽ chuyển sang yêu cầu chức năng cốt lõi của phần mềm - Thêm thẻ mới vào danh sách trên bảng Trello. Chúng tôi sẽ sử dụng các bước tương tự từ lệnh cho đến khi lấy dữ liệu từ bảng. list Ngoài ra, chúng tôi sẽ yêu cầu người dùng cung cấp thông tin tương tác để định cấu hình thẻ mới đúng cách: Danh sách Trello sẽ được thêm vào: Lựa chọn một lần Tên thẻ: Văn bản [Tùy chọn] Mô tả thẻ: Văn bản [Tùy chọn] Nhãn: Nhiều lựa chọn Xác nhận: có/không Đối với tất cả các lời nhắc cần người dùng chọn từ danh sách, chúng tôi sẽ sử dụng gói như trước đây. Đối với các lời nhắc và mục linh tinh khác như cần nhập văn bản hoặc xác nhận của người dùng, chúng tôi sẽ chỉ sử dụng gói . Điều quan trọng cần lưu ý là chúng ta phải xử lý đúng các giá trị tùy chọn: Simple Terminal Menu rich Người dùng có thể bỏ qua việc cung cấp mô tả Người dùng có thể cung cấp lựa chọn trống cho Nhãn # trellocli/cli/cli_create.py @app.command() def card( board_name: Annotated[str, Option()] = "" ) -> None: """COMMAND to add a new trello card OPTIONS board_name (str): board to use """ try: # check authorization res_is_authorized = trellojob.is_authorized() if not res_is_authorized: print("[bold red]Error![/] Authorization hasn't been granted. Try running `trellocli config access`") raise AuthorizationError # if board_name OPTION was given, attempt to retrieve board id using the name # else attempt to retrieve board id stored as an env var board_id = None if not board_name: load_dotenv() if not os.getenv("TRELLO_BOARD_ID"): print("[bold red]Error![/] A trello board hasn't been configured to use. Try running `trellocli config board`") raise InvalidUserInputError board_id = os.getenv("TRELLO_BOARD_ID") else: res_get_all_boards = trellojob.get_all_boards() if res_get_all_boards.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving boards from trello") raise TrelloReadError boards_list = {board.name: board.id for board in res_get_all_boards.res} # retrieve all board id(s) and find matching board name if board_name not in boards_list: print("[bold red]Error![/] An invalid trello board name was provided. Try running `trellocli config board`") raise InvalidUserInputError board_id = boards_list[board_name] # configure board to use res_get_board = trellojob.get_board(board_id=board_id) if res_get_board.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when configuring the trello board to use") raise TrelloReadError board = res_get_board.res # retrieve data (labels, trellolists) from board res_get_all_labels = trellojob.get_all_labels(board=board) if res_get_all_labels.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving the labels from the trello board") raise TrelloReadError labels_list = res_get_all_labels.res labels_dict = {label.name: label for label in labels_list if label.name} res_get_all_lists = trellojob.get_all_lists(board=board) if res_get_all_lists.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving the lists from the trello board") raise TrelloReadError trellolists_list = res_get_all_lists.res trellolists_dict = {trellolist.name: trellolist for trellolist in trellolists_list} # for easy access to trellolist when given name of trellolist # request for user input (trellolist, card name, description, labels to include) interactively to configure new card to be added trellolist_menu = TerminalMenu( trellolists_dict.keys(), title="Select list:", raise_error_on_interrupt=True ) # Prompt: trellolist trellolist_menu.show() print(trellolist_menu.chosen_menu_entry) selected_trellolist = trellolists_dict[trellolist_menu.chosen_menu_entry] selected_name = Prompt.ask("Card name") # Prompt: card name selected_desc = Prompt.ask("Description (Optional)", default=None) # Prompt (Optional) description labels_menu = TerminalMenu( labels_dict.keys(), title="Select labels (Optional):", multi_select=True, multi_select_empty_ok=True, multi_select_select_on_accept=False, show_multi_select_hint=True, raise_error_on_interrupt=True ) # Prompt (Optional): labels labels_menu.show() selected_labels = [labels_dict[label] for label in list(labels_menu.chosen_menu_entries)] if labels_menu.chosen_menu_entries else None # display user selection and request confirmation print() confirmation_table = Table(title="Card to be Added", show_header=False) confirmation_table.add_row("List", selected_trellolist.name) confirmation_table.add_row("Name", selected_name) confirmation_table.add_row("Description", selected_desc) confirmation_table.add_row("Labels", ", ".join([label.name for label in selected_labels]) if selected_labels else None) console.print(confirmation_table) confirm = Confirm.ask("Confirm") # if confirm, attempt to add card to trello # else, exit if confirm: res_add_card = trellojob.add_card( col=selected_trellolist, name=selected_name, desc=selected_desc, labels=selected_labels ) if res_add_card.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when adding a new card to trello") raise TrelloWriteError else: print("Process cancelled...") except KeyboardInterrupt: print("[yellow]Keyboard Interrupt.[/] Program exited...") except (AuthorizationError, InvalidUserInputError, TrelloReadError, TrelloWriteError): print("Program exited...") 💡Bạn có thể hiển thị thanh tiến trình cho quá trình không? Gợi ý: Hãy xem cách sử dụng của Góc thử thách add tính năng trạng thái rich Phân phối gói Phần thú vị nhất đến đây - phân phối chính thức phần mềm của chúng tôi trên PyPI. Chúng tôi sẽ theo đường dẫn này để làm như vậy: Định cấu hình siêu dữ liệu + cập nhật README Tải lên để kiểm tra PyPI Định cấu hình hành động GitHub Đẩy mã vào Tag v1.0.0 Phân phối mã cho PyPI 🎉 Để được giải thích chi tiết, hãy xem hướng dẫn tuyệt vời này về Python Packaging của Ramit Mittal. Cấu hình siêu dữ liệu Chi tiết cuối cùng mà chúng ta cần cho là chỉ định mô-đun nào lưu trữ gói đó. Trong trường hợp của chúng tôi, đó sẽ là . Đây là siêu dữ liệu cần thêm: pyproject.toml trellocli # pyproject.toml [tool.setuptools] packages = ["trellocli"] Đối với của chúng tôi, cách tốt nhất là cung cấp một số loại hướng dẫn, có thể là hướng dẫn sử dụng hoặc cách bắt đầu. Nếu bạn đã đưa hình ảnh vào thì bạn nên sử dụng URL tuyệt đối của nó, thường có định dạng sau README.md README.md https://raw.githubusercontent.com/<user>/<repo>/<branch>/<path-to-image> TestPyPI Chúng tôi sẽ sử dụng các công cụ và để xây dựng và xuất bản gói của mình. Chạy lệnh sau trong thiết bị đầu cuối của bạn để tạo kho lưu trữ nguồn và bánh xe cho gói của bạn: build twine python -m build Đảm bảo rằng bạn đã thiết lập tài khoản trên TestPyPI và chạy lệnh sau twine upload -r testpypi dist/* Bạn sẽ được nhắc nhập tên người dùng và mật khẩu của mình. Do đã bật xác thực hai yếu tố, bạn sẽ được yêu cầu sử dụng Mã thông báo API (Để biết thêm thông tin về cách nhận mã thông báo API TestPyPI: ). Đơn giản chỉ cần đặt vào các giá trị sau: liên kết đến tài liệu tên tài khoản: mã thông báo mật khẩu: <mã thông báo TestPyPI của bạn> Sau khi hoàn tất, bạn có thể truy cập TestPyPI để kiểm tra gói mới được phân phối của mình! Cài đặt GitHub Mục tiêu là sử dụng GitHub như một phương tiện để cập nhật liên tục các phiên bản mới của gói dựa trên thẻ. Trước tiên, hãy đi tới tab trên quy trình làm việc GitHub của bạn và chọn quy trình công việc mới. Chúng tôi sẽ sử dụng quy trình công việc được tạo bởi GitHub Actions. Lưu ý rằng quy trình làm việc yêu cầu đọc từ kho lưu trữ bí mật như thế nào? Đảm bảo rằng bạn đã lưu trữ mã thông báo PyPI của mình dưới tên được chỉ định (Việc nhận mã thông báo API PyPI tương tự như TestPyPI). Actions Publish Python Package Sau khi tạo quy trình làm việc, chúng tôi sẽ đẩy mã của mình vào thẻ v1.0.0. Để biết thêm thông tin về cú pháp đặt tên phiên bản, đây là lời giải thích tuyệt vời của Py-Pkgs: link to document Đơn giản chỉ cần chạy các , và thông thường. Tiếp theo, tạo thẻ cho cam kết mới nhất của bạn bằng cách chạy lệnh sau (Để biết thêm thông tin về thẻ: ) pull add commit liên kết đến tài liệu git tag <tagname> HEAD Cuối cùng, đẩy thẻ mới của bạn vào kho lưu trữ từ xa git push <remote name> <tag name> Đây là một bài viết tuyệt vời của nếu bạn muốn tìm hiểu thêm. Nhưng bây giờ, hãy ngồi lại và tận hưởng thành tích mới nhất của bạn 🎉. Vui lòng xem điều kỳ diệu được làm sáng tỏ dưới dạng quy trình làm việc của Hành động GitHub khi nó phân phối gói của bạn tới PyPI. Karol Horosin về Tích hợp CI/CD với Gói Python của bạn Gói (lại Đây là một cái dài 😓. Thông qua hướng dẫn này, bạn đã học cách chuyển đổi phần mềm của mình thành chương trình CLI bằng cách sử dụng mô-đun và phân phối gói của bạn tới PyPI. Để tìm hiểu sâu hơn, bạn đã học cách xác định lệnh và nhóm lệnh, phát triển phiên CLI tương tác và tìm hiểu các tình huống CLI phổ biến như gián đoạn bàn phím. Typer Bạn quả thực là một phù thủy tuyệt vời trong việc vượt qua tất cả. Bạn sẽ không tham gia cùng tôi trong Phần 3, nơi chúng tôi triển khai các chức năng tùy chọn chứ?