Hi, chúng ta hãy chơi! Chọn máy bay chiến đấu của bạn (oẳn tù tì, kéo): đá
Nhiều phần mềm hàng ngày được gói dưới dạng chương trình CLI. Lấy ví dụ về trình soạn thảo văn bản vim
- một công cụ đi kèm với bất kỳ hệ thống UNIX nào có thể được kích hoạt đơn giản bằng cách chạy vim <FILE>
trong thiết bị đầu cuối.
Ví dụ: khi chúng tôi muốn đặt thuộc tính project
trong phần cốt lõi, chúng tôi chạy gcloud config set project <PROJECT_ID>
Lý lẽ | Nội dung |
---|---|
Đối số 0 | gcloud |
Đối số 1 | cấu hình |
… | … |
Dựa trên ví dụ trước, chúng tôi đặt thuộc tính project
trong phần cốt lõi bằng cách chạy gcloud config set project <PROJECT_ID>
Nói cách khác, set
là một lệnh.
Quay lại lệnh gcloud config
, như đã nêu trong tài liệu chính thức của họ, gcloud config
là một nhóm lệnh cho phép bạn sửa đổi các thuộc tính. Việc sử dụng là như vậy:
gcloud config GROUP | COMMAND [GCLOUD_WIDE_FLAG … ]
theo đó LỆNH có thể là set
, list
, v.v. (Lưu ý rằng GROUP là config
)
Quay lại việc sử dụng nhóm lệnh gcloud config
, (các) tùy chọn, trong trường hợp này, là GCLOUD_WIDE_FLAG
.
Ví dụ: giả sử rằng chúng tôi muốn hiển thị cách sử dụng và mô tả chi tiết của lệnh, chúng tôi chạy gcloud config set –help
. Nói cách khác, --help
là tùy chọn.
Một ví dụ khác là khi chúng tôi muốn đặt thuộc tính vùng trong phần tính toán của một dự án cụ thể, chúng tôi chạy gcloud config set compute <ZONE_NAME> –project=<PROJECT_ID>
. Nói cách khác, --project
là một tùy chọn chứa giá trị <PROJECT_ID>
.
Ví dụ: khi chúng tôi muốn tạo một cụm dataproc, chúng tôi chạy gcloud dataproc clusters create <CLUSTER_NAME> –region=<REGION>
. Và như đã nêu trong tài liệu sử dụng của họ:
gcloud dataproc clusters create (CLUSTER: –region=REGION)
Cờ --region
là bắt buộc nếu nó chưa được định cấu hình trước đó.
Các tùy chọn ngắn bắt đầu bằng -
theo sau là một ký tự chữ và số, trong khi các tùy chọn dài bắt đầu bằng --
theo sau là nhiều ký tự. Hãy coi các tùy chọn ngắn là lối tắt khi người dùng chắc chắn về những gì họ muốn trong khi các tùy chọn dài dễ đọc hơn.
Bạn đã chọn đá! Bây giờ máy tính sẽ thực hiện lựa chọn của nó.
Nhóm của bạn sử dụng Trello để theo dõi các vấn đề và tiến độ của dự án. Nhóm của bạn đang tìm kiếm một cách đơn giản hơn để tương tác với bảng - tương tự như việc tạo kho lưu trữ GitHub mới thông qua thiết bị đầu cuối. Nhóm đã nhờ bạn tạo một chương trình CLI với yêu cầu cơ bản này là có thể thêm thẻ mới vào cột 'Việc cần làm' trên bảng.
Yêu cầu chức năng
Những yêu cầu phi lý
Yêu cầu tùy chọn
Ps Đừng lo lắng về hai cột cuối cùng, chúng ta sẽ tìm hiểu về nó sau…
bài kiểm tra đơn vị
Trello
CLI
Tiện ích (Khác)
Phần 1
py-trello
Phần 2
Phần 3
Máy tính đã chọn kéo! Hãy xem ai là người chiến thắng trong trận chiến này…
trellocli/ __init__.py __main__.py models.py cli.py trelloservice.py tests/ test_cli.py test_trelloservice.py README.md pyproject.toml .env .gitignore
trellocli
: đóng vai trò là tên gói được người dùng sử dụng, ví dụ: pip install trellocli
__init__.py
: đại diện cho thư mục gốc của gói, tuân thủ thư mục dưới dạng gói Python__main__.py
: xác định điểm vào và cho phép người dùng chạy các mô-đun mà không cần chỉ định đường dẫn tệp bằng cách sử dụng cờ -m
, ví dụ: python -m <module_name>
để thay thế python -m <parent_folder>/<module_name>.py
models.py
: lưu trữ các lớp được sử dụng trên toàn cầu, ví dụ: các mô hình mà các phản hồi API dự kiến sẽ tuân theocli.py
: lưu trữ logic nghiệp vụ cho các lệnh và tùy chọn CLItrelloservice.py
: lưu trữ logic nghiệp vụ để tương tác với py-trello
tests
: lưu trữ các bài kiểm tra đơn vị cho chương trìnhtest_cli.py
: lưu trữ các bài kiểm tra đơn vị để triển khai CLItest_trelloservice.py
: lưu trữ các bài kiểm tra đơn vị để tương tác với py-trello
README.md
: lưu trữ tài liệu cho chương trìnhpyproject.toml
: lưu trữ các cấu hình và yêu cầu của gói.env
: lưu trữ các biến môi trường.gitignore
: chỉ định các tệp bị bỏ qua (không được theo dõi) trong quá trình kiểm soát phiên bản
Bắt đầu với tệp __init__.py
trong gói của chúng tôi, đây sẽ là nơi lưu trữ các biến và hằng số của gói, chẳng hạn như tên và phiên bản ứng dụng. Trong trường hợp của chúng tôi, chúng tôi muốn khởi tạo như sau:
# trellocli/__init__.py __app_name__ = "trellocli" __version__ = "0.1.0" ( SUCCESS, TRELLO_WRITE_ERROR, TRELLO_READ_ERROR ) = range(3) ERRORS = { TRELLO_WRITE_ERROR: "Error when writing to Trello", TRELLO_READ_ERROR: "Error when reading from Trello" }
Chuyển sang tệp __main__.py
, luồng chính của chương trình của bạn sẽ được lưu trữ tại đây. Trong trường hợp của chúng tôi, chúng tôi sẽ lưu trữ điểm vào chương trình CLI, giả sử rằng sẽ có một chức năng có thể gọi được trong cli.py
.
# trellocli/__main__.py from trellocli import cli def main(): # we'll modify this later - after the implementation of `cli.py` pass if __name__ == "__main__": main()
Bây giờ gói đã được thiết lập, hãy xem cập nhật tệp README.md
của chúng tôi (tài liệu chính). Không có một cấu trúc cụ thể nào mà chúng ta phải tuân theo, nhưng một README tốt sẽ bao gồm những phần sau:
<!--- README.md --> # Overview # Getting Started # Usage # Architecture ## Data Flow ## Tech Stack # Running Tests # Next Steps # References
# pyproject.toml [project] name = "trellocli_<YOUR_USERNAME>" version = "0.1.0" authors = [ { name = "<YOUR_NAME>", email = "<YOUR_EMAIL>" } ] description = "Program to modify your Trello boards from your computer's command line" readme = "README.md" requires-python = ">=3.7" classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] dependencies = [] [project.urls] "Homepage" = ""
Tiếp theo trong danh sách sẽ là tệp .env
của chúng tôi, nơi chúng tôi lưu trữ các biến môi trường của mình, chẳng hạn như khóa và bí mật API. Điều quan trọng cần lưu ý là Git không nên theo dõi tệp này vì nó chứa thông tin nhạy cảm.
Trong trường hợp của chúng tôi, chúng tôi sẽ lưu trữ thông tin đăng nhập Trello của mình tại đây. Để tạo Power-Up trong Trello, hãy làm theo . Cụ thể hơn, dựa trên việc sử dụng py-trello
, vì chúng tôi dự định sử dụng OAuth cho ứng dụng của mình, chúng tôi sẽ cần những thứ sau để tương tác với Trello:
Khi bạn đã truy xuất Khóa API và Bí mật của mình, hãy lưu trữ chúng trong tệp .env
như vậy
# .env TRELLO_API_KEY=<your_api_key> TRELLO_API_SECRET=<your_api_secret>
Cuối cùng nhưng không kém phần quan trọng, hãy sử dụng mẫu Python .gitignore
có thể tìm thấy . Lưu ý rằng điều này rất quan trọng để đảm bảo rằng tệp .env
của chúng tôi không bao giờ bị theo dõi - nếu tại một thời điểm nào đó, tệp .env
của chúng tôi bị theo dõi, ngay cả khi chúng tôi đã xóa tệp ở các bước sau, thì thiệt hại vẫn được thực hiện và các tác nhân độc hại có thể lần theo dấu vết trước đó bản vá cho thông tin nhạy cảm.
Bây giờ, quá trình thiết lập đã hoàn tất, hãy đẩy các thay đổi của chúng tôi lên GitHub. Tùy thuộc vào siêu dữ liệu được chỉ định trong pyproject.toml
, hãy nhớ cập nhật GIẤY PHÉP và URL trang chủ của bạn cho phù hợp. Để tham khảo về cách viết các cam kết tốt hơn:
Dựa trên các yêu cầu chức năng, mối quan tâm chính của chúng tôi là cho phép người dùng thêm thẻ mới. Tham khảo phương thức trong py-trello
: . Để có thể làm như vậy, chúng ta phải gọi phương thức add_card
từ lớp List
, phương thức này có thể được truy xuất từ hàm get_list
từ lớp Board
, phương thức này có thể được truy xuất…
Trước khi đi sâu vào mã, hãy sửa đổi tệp pyproject.toml
của chúng tôi để bao gồm các phụ thuộc cần thiết cho việc viết/chạy thử nghiệm đơn vị.
# pyproject.toml [project] dependencies = [ "pytest==7.4.0", "pytest-mock==3.11.1" ]
Tiếp theo, hãy kích hoạt virtualenv của chúng tôi và chạy pip install .
để cài đặt các phụ thuộc.
# tests/test_trelloservice.py # module imports from trellocli import SUCCESS from trellocli.trelloservice import TrelloService from trellocli.models import * # dependencies imports # misc imports def test_get_access_token(mocker): """Test to check success retrieval of user's access token""" mock_res = GetOAuthTokenResponse( token="test", token_secret="test", status_code=SUCCESS ) mocker.patch( "trellocli.trelloservice.TrelloService.get_user_oauth_token", return_value=mock_res ) trellojob = TrelloService() res = trellojob.get_user_oauth_token() assert res.status_code == SUCCESS
Lưu ý trong mã mẫu của tôi rằng GetOAuthTokenResponse
là một mô hình chưa được đặt trong models.py
. Nó cung cấp cấu trúc để viết mã sạch hơn, chúng ta sẽ thấy nó hoạt động sau.
Để chạy thử nghiệm của chúng tôi, chỉ cần chạy python -m pytest
. Lưu ý rằng các bài kiểm tra của chúng tôi sẽ thất bại như thế nào, nhưng không sao - cuối cùng nó sẽ ổn thôi.
Góc thử thách 💡 Bạn thử tự viết thêm test xem sao? Vui lòng tham khảo để xem thử nghiệm của tôi trông như thế nào
Bây giờ, hãy xây dựng trelloservice
của chúng ta. Bắt đầu với việc thêm một phụ thuộc mới, đó là trình bao bọc py-trello
.
# pyproject.toml dependencies = [ "pytest==7.4.0", "pytest-mock==3.11.1", "py-trello==0.19.0" ]
Một lần nữa, chạy pip install .
để cài đặt các phụ thuộc.
Bây giờ, hãy bắt đầu bằng cách xây dựng các mô hình của chúng tôi - để điều chỉnh các phản hồi mà chúng tôi mong đợi trong trelloservice
. Đối với phần này, tốt nhất bạn nên tham khảo các bài kiểm tra đơn vị của chúng tôi và mã nguồn py-trello
để hiểu loại giá trị trả về mà chúng tôi có thể mong đợi.
Ví dụ: giả sử rằng chúng tôi muốn truy xuất mã thông báo truy cập của người dùng, tham khảo hàm create_oauth_token
của py-trello
( ), chúng tôi biết giá trị trả về sẽ giống như thế này
# trellocli/models.py # module imports # dependencies imports # misc imports from typing import NamedTuple class GetOAuthTokenResponse(NamedTuple): token: str token_secret: str status_code: int
Mặt khác, hãy lưu ý các quy ước đặt tên mâu thuẫn. Ví dụ: mô-đun py-trello
có một lớp có tên List
. Giải pháp thay thế cho vấn đề này là cung cấp bí danh trong quá trình nhập.
# trellocli/models.py # dependencies imports from trello import List as Trellolist
# trellocli/models.py class GetBoardName(NamedTuple): """Model to store board id Attributes id (str): Extracted board id from Board value type """ id: str
Góc thử thách 💡 Bạn thử tự viết thêm các mẫu nữa xem sao? Vui lòng tham khảo để xem mô hình của tôi trông như thế nào
Mô hình ngừng hoạt động, hãy chính thức bắt đầu mã hóa trelloservice
. Một lần nữa, chúng ta nên tham khảo các bài kiểm tra đơn vị mà chúng tôi đã tạo - giả sử rằng danh sách các bài kiểm tra hiện tại không cung cấp phạm vi bảo hiểm đầy đủ cho dịch vụ, luôn quay lại và thêm các bài kiểm tra khác khi cần.
Theo thông lệ, hãy đưa tất cả các câu lệnh nhập vào đầu trang. Sau đó, tạo lớp TrelloService
và các phương thức giữ chỗ như mong đợi. Ý tưởng là chúng ta sẽ khởi tạo một phiên bản dùng chung của dịch vụ trong cli.py
và gọi các phương thức của nó tương ứng. Hơn nữa, chúng tôi đang hướng tới khả năng mở rộng, do đó cần có phạm vi bảo hiểm rộng rãi.
# trellocli/trelloservice.py # module imports from trellocli import TRELLO_READ_ERROR, TRELLO_WRITE_ERROR, SUCCESS from trellocli.models import * # dependencies imports from trello import TrelloClient # misc imports class TrelloService: """Class to implement the business logic needed to interact with Trello""" def __init__(self) -> None: pass def get_user_oauth_token() -> GetOAuthTokenResponse: pass def get_all_boards() -> GetAllBoardsResponse: pass def get_board() -> GetBoardResponse: pass def get_all_lists() -> GetAllListsResponse: pass def get_list() -> GetListResponse: pass def get_all_labels() -> GetAllLabelsResponse: pass def get_label() -> GetLabelResponse: pass def add_card() -> AddCardResponse: pass
Xin lưu ý rằng lần này khi chúng tôi chạy thử nghiệm, các thử nghiệm của chúng tôi sẽ vượt qua như thế nào. Trên thực tế, điều này sẽ giúp chúng tôi đảm bảo rằng chúng tôi đi đúng hướng. Quy trình làm việc phải là mở rộng các chức năng của chúng tôi, chạy thử nghiệm, kiểm tra đạt/không đạt và cấu trúc lại cho phù hợp.
Hãy bắt đầu với hàm __init__
. Ý tưởng là gọi hàm get_user_oauth_token
tại đây và khởi tạo TrelloClient
. Một lần nữa, nhấn mạnh nhu cầu chỉ lưu trữ thông tin nhạy cảm như vậy trong tệp .env
, chúng tôi sẽ sử dụng phần phụ thuộc python-dotenv
để truy xuất thông tin nhạy cảm. Sau khi sửa đổi tệp pyproject.toml
của chúng tôi cho phù hợp, hãy bắt đầu thực hiện các bước ủy quyền.
# trellocli/trelloservice.py class TrelloService: """Class to implement the business logic needed to interact with Trello""" def __init__(self) -> None: self.__load_oauth_token_env_var() self.__client = TrelloClient( api_key=os.getenv("TRELLO_API_KEY"), api_secret=os.getenv("TRELLO_API_SECRET"), token=os.getenv("TRELLO_OAUTH_TOKEN") ) def __load_oauth_token_env_var(self) -> None: """Private method to store user's oauth token as an environment variable""" load_dotenv() if not os.getenv("TRELLO_OAUTH_TOKEN"): res = self.get_user_oauth_token() if res.status_code == SUCCESS: dotenv_path = find_dotenv() set_key( dotenv_path=dotenv_path, key_to_set="TRELLO_OAUTH_TOKEN", value_to_set=res.token ) else: print("User denied access.") self.__load_oauth_token_env_var() def get_user_oauth_token(self) -> GetOAuthTokenResponse: """Helper method to retrieve user's oauth token Returns GetOAuthTokenResponse: user's oauth token """ try: res = create_oauth_token() return GetOAuthTokenResponse( token=res["oauth_token"], token_secret=res["oauth_token_secret"], status_code=SUCCESS ) except: return GetOAuthTokenResponse( token="", token_secret="", status_code=TRELLO_AUTHORIZATION_ERROR )
Trong quá trình triển khai này, chúng tôi đã tạo một phương thức trợ giúp để xử lý mọi lỗi có thể thấy trước, ví dụ: khi người dùng nhấp vào Deny
trong khi ủy quyền. Hơn nữa, nó đượ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 trả về phản hồi hợp lệ, vì thực tế là chúng tôi không thể tiếp tục trừ khi người dùng cho phép ứng dụng của chúng tôi truy cập dữ liệu tài khoản của họ.
Góc thử thách 💡 Thông báo TRELLO_AUTHORIZATION_ERROR
? Bạn có thể khai báo lỗi này là hằng số gói không? Tham khảo Thiết lập để biết thêm thông tin
# trellocli/trelloservice.py def get_all_boards(self) -> GetAllBoardsResponse: """Method to list all boards from user's account Returns GetAllBoardsResponse: array of user's trello boards """ try: res = self.__client.list_boards() return GetAllBoardsResponse( res=res, status_code=SUCCESS ) except: return GetAllBoardsResponse( res=[], status_code=TRELLO_READ_ERROR ) def get_board(self, board_id: str) -> GetBoardResponse: """Method to retrieve board Required Args board_id (str): board id Returns GetBoardResponse: trello board """ try: res = self.__client.get_board(board_id=board_id) return GetBoardResponse( res=res, status_code=SUCCESS ) except: return GetBoardResponse( res=None, status_code=TRELLO_READ_ERROR )
Đối với việc truy xuất các danh sách (cột), chúng ta sẽ phải kiểm tra lớp Board
của py-trello
, hay nói cách khác, chúng ta phải chấp nhận một tham số mới của loại giá trị Board
.
# trellocli/trelloservice.py def get_all_lists(self, board: Board) -> GetAllListsResponse: """Method to list all lists (columns) from the trello board Required Args board (Board): trello board Returns GetAllListsResponse: array of trello lists """ try: res = board.all_lists() return GetAllListsResponse( res=res, status_code=SUCCESS ) except: return GetAllListsResponse( res=[], status_code=TRELLO_READ_ERROR ) def get_list(self, board: Board, list_id: str) -> GetListResponse: """Method to retrieve list (column) from the trello board Required Args board (Board): trello board list_id (str): list id Returns GetListResponse: trello list """ try: res = board.get_list(list_id=list_id) return GetListResponse( res=res, status_code=SUCCESS ) except: return GetListResponse( res=None, status_code=TRELLO_READ_ERROR )
Góc thử thách 💡 Bạn có thể tự triển khai hàm get_all_labels
và get_label
không? Sửa lại lớp Board
của py-trello
. Vui lòng tham khảo để xem triển khai của tôi trông như thế nào
# trellocli/trelloservice.py def add_card( self, col: Trellolist, name: str, desc: str = "", labels: List[Label] = [] ) -> AddCardResponse: """Method to add a new card to a list (column) on the trello board Required Args col (Trellolist): trello list name (str): card name Optional Args desc (str): card description labels (List[Label]): list of labels to be added to the card Returns AddCardResponse: newly-added card """ try: # create new card new_card = col.add_card(name=name) # add optional description if desc: new_card.set_description(description=desc) # add optional labels if labels: for label in labels: new_card.add_label(label=label) return AddCardResponse( res=new_card, status_code=SUCCESS ) except: return AddCardResponse( res=new_card, status_code=TRELLO_WRITE_ERROR )
Chúc mừng! Bạn đã thắng. Chơi lại (y/N)?