Hallo, lass uns spielen! Wählen Sie Ihren Kämpfer (Stein, Papier, Schere): Stein
Viele alltägliche Software ist als CLI-Programm verpackt. Nehmen Sie zum Beispiel den vim
Texteditor – ein Tool, das mit jedem UNIX-System geliefert wird und einfach durch Ausführen vim <FILE>
im Terminal aktiviert werden kann.
Wenn wir beispielsweise die project
im Kernabschnitt festlegen möchten, führen wir gcloud config set project <PROJECT_ID>
aus
Streit | Inhalt |
---|---|
Argument 0 | gcloud |
Argument 1 | config |
… | … |
Basierend auf dem vorherigen Beispiel legen wir die project
im Kernabschnitt fest, indem wir gcloud config set project <PROJECT_ID>
ausführen
Mit anderen Worten, set
ist ein Befehl.
Bezugnehmend auf den Befehl gcloud config
, wie in der offiziellen Dokumentation angegeben, handelt es sich bei gcloud config
um eine Befehlsgruppe, mit der Sie Eigenschaften ändern können. Die Verwendung ist als solche:
gcloud config GROUP | COMMAND [GCLOUD_WIDE_FLAG … ]
wobei COMMAND entweder set
, list
usw. sein kann ... (Beachten Sie, dass GROUP config
ist)
Zurück zur Verwendung der gcloud config
Befehlsgruppe: Die Option(en) sind in diesem Fall GCLOUD_WIDE_FLAG
.
Angenommen, wir möchten die detaillierte Verwendung und Beschreibung des Befehls anzeigen, führen wir gcloud config set –help
aus. Mit anderen Worten: --help
ist die Option.
Ein weiteres Beispiel: Wenn wir die Zoneneigenschaft im Compute-Abschnitt eines bestimmten Projekts festlegen möchten, führen wir gcloud config set compute <ZONE_NAME> –project=<PROJECT_ID>
aus. Mit anderen Worten: --project
ist eine Option, die den Wert <PROJECT_ID>
enthält.
Wenn wir beispielsweise einen Dataproc-Cluster erstellen möchten, führen wir gcloud dataproc clusters create <CLUSTER_NAME> –region=<REGION>
aus. Und wie in ihrer Nutzungsdokumentation angegeben:
gcloud dataproc clusters create (CLUSTER: –region=REGION)
Das Flag --region
ist obligatorisch, wenn es nicht zuvor konfiguriert wurde.
Kurze Optionen beginnen mit -
gefolgt von einem einzelnen alphanumerischen Zeichen, während lange Optionen mit --
gefolgt von mehreren Zeichen beginnen. Stellen Sie sich kurze Optionen als Abkürzungen vor, wenn der Benutzer sicher ist, was er möchte, während lange Optionen besser lesbar sind.
Du hast dich für Rock entschieden! Der Computer trifft nun seine Auswahl.
Ihr Team verwendet Trello, um die Probleme und den Fortschritt des Projekts zu verfolgen. Ihr Team sucht nach einer einfacheren Möglichkeit, mit dem Board zu interagieren – ähnlich wie beim Erstellen eines neuen GitHub-Repositorys über das Terminal. Das Team hat sich an Sie gewandt, um ein CLI-Programm mit der Grundvoraussetzung zu erstellen, dass Sie der Spalte „Zu erledigen“ des Boards eine neue Karte hinzufügen können.
Funktionale Anforderungen
Nicht-funktionale Anforderungen
Optionale Anforderungen
Ps. Machen Sie sich keine Sorgen wegen der letzten beiden Spalten, wir werden später mehr darüber erfahren …
Unit-Tests
Trello
CLI
Dienstprogramme (Verschiedenes)
Teil 1
py-trello
GeschäftslogikTeil 2
Teil 3
Der Computer hat sich für die Schere entschieden! Mal sehen, wer diesen Kampf gewinnt ...
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
: fungiert als Paketname, der von Benutzern verwendet werden soll, z. B. pip install trellocli
__init__.py
: Stellt das Stammverzeichnis des Pakets dar und passt den Ordner als Python-Paket an__main__.py
: Definiert den Einstiegspunkt und ermöglicht Benutzern das Ausführen von Modulen ohne Angabe des Dateipfads durch Verwendung des Flags -m
, z. B. python -m <module_name>
, um python -m <parent_folder>/<module_name>.py
zu ersetzenmodels.py
: speichert global verwendete Klassen, z. B. Modelle, denen API-Antworten entsprechen sollencli.py
: speichert die Geschäftslogik für CLI-Befehle und -Optionentrelloservice.py
: speichert die Geschäftslogik für die Interaktion mit py-trello
tests
: speichert Unit-Tests für das Programmtest_cli.py
: speichert Unit-Tests für die CLI-Implementierungtest_trelloservice.py
: speichert Unit-Tests für die Interaktion mit py-trello
README.md
: speichert die Dokumentation für das Programmpyproject.toml
: speichert die Konfigurationen und Anforderungen des Pakets.env
: speichert Umgebungsvariablen.gitignore
: Gibt die Dateien an, die während der Versionskontrolle ignoriert (nicht verfolgt) werden sollen
Beginnend mit der Datei __init__.py
in unserem Paket, in der Paketkonstanten und -variablen wie App-Name und -Version gespeichert werden. In unserem Fall wollen wir Folgendes initialisieren:
# 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" }
Wenn wir zur Datei __main__.py
übergehen, sollte hier der Hauptablauf Ihres Programms gespeichert sein. In unserem Fall speichern wir den Einstiegspunkt des CLI-Programms, vorausgesetzt, dass es in cli.py
eine aufrufbare Funktion gibt.
# 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()
Nachdem das Paket nun eingerichtet wurde, werfen wir einen Blick auf die Aktualisierung unserer README.md
Datei (Hauptdokumentation). Es gibt keine bestimmte Struktur, der wir folgen müssen, aber eine gute README-Datei würde aus Folgendem bestehen:
<!--- 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" = ""
Als nächstes auf der Liste wäre unsere .env
Datei, in der wir unsere Umgebungsvariablen wie API-Geheimnisse und Schlüssel speichern. Es ist wichtig zu beachten, dass diese Datei nicht von Git verfolgt werden sollte, da sie vertrauliche Informationen enthält.
In unserem Fall speichern wir hier unsere Trello-Zugangsdaten. Befolgen Sie , um ein Power-Up in Trello zu erstellen. Genauer gesagt, basierend auf der Verwendung durch py-trello
, da wir OAuth für unsere Anwendung verwenden möchten, benötigen wir Folgendes, um mit Trello zu interagieren:
Sobald Sie Ihren API-Schlüssel und Ihr Geheimnis abgerufen haben, speichern Sie sie als solche in der .env
Datei
# .env TRELLO_API_KEY=<your_api_key> TRELLO_API_SECRET=<your_api_secret>
Zu guter Letzt verwenden wir die Vorlage Python .gitignore
, die Sie finden. Beachten Sie, dass dies von entscheidender Bedeutung ist, um sicherzustellen, dass unsere .env
Datei niemals verfolgt wird. Wenn unsere .env
Datei irgendwann verfolgt wurde, ist der Schaden bereits angerichtet und böswillige Akteure können die vorherige Datei aufspüren, auch wenn wir sie in späteren Schritten entfernt haben Patches für vertrauliche Informationen.
Nachdem die Einrichtung nun abgeschlossen ist, übertragen wir unsere Änderungen auf GitHub. Denken Sie je nach den in pyproject.toml
angegebenen Metadaten daran, Ihre LIZENZ und Homepage-URL entsprechend zu aktualisieren. Als Referenz zum Schreiben besserer Commits:
Basierend auf den funktionalen Anforderungen besteht unser Hauptanliegen darin, Benutzern das Hinzufügen einer neuen Karte zu ermöglichen. Verweisen auf die Methode in py-trello
: . Dazu müssen wir die Methode add_card
aus der Klasse List
aufrufen, die über die Funktion get_list
aus der Klasse „ Board
abgerufen werden kann.
Bevor wir in den Code eintauchen, ändern wir unsere Datei pyproject.toml
so, dass sie die Abhängigkeiten enthält, die zum Schreiben/Ausführen von Komponententests erforderlich sind.
# pyproject.toml [project] dependencies = [ "pytest==7.4.0", "pytest-mock==3.11.1" ]
Als nächstes aktivieren wir unsere virtuelle Umgebung und führen pip install .
um die Abhängigkeiten zu installieren.
# 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
Beachten Sie in meinem Beispielcode, dass GetOAuthTokenResponse
ein Modell ist, das noch in models.py
festgelegt werden muss. Es bietet Struktur zum Schreiben sauberer Codes. Wir werden dies später in Aktion sehen.
Um unsere Tests auszuführen, führen Sie einfach python -m pytest
aus. Beachten Sie, dass unsere Tests scheitern werden, aber das ist in Ordnung – am Ende wird es klappen.
Challenge Corner 💡 Können Sie versuchen, weitere Tests selbst zu schreiben? Sehen Sie sich gerne an, um zu sehen, wie meine Tests aussehen
Lassen Sie uns zunächst unseren trelloservice
aufbauen. Beginnen Sie mit dem Hinzufügen einer neuen Abhängigkeit, dem py-trello
-Wrapper.
# pyproject.toml dependencies = [ "pytest==7.4.0", "pytest-mock==3.11.1", "py-trello==0.19.0" ]
Führen Sie erneut pip install .
um die Abhängigkeiten zu installieren.
Beginnen wir nun mit dem Aufbau unserer Modelle – um die Antworten zu regulieren, die wir in trelloservice
erwarten. Für diesen Teil lesen Sie am besten unsere Unit-Tests und den py-trello
Quellcode, um zu verstehen, welche Art von Rückgabewert wir erwarten können.
Angenommen, wir möchten das Zugriffstoken des Benutzers abrufen und beziehen uns dabei auf die Funktion create_oauth_token
von py-trello
( ). Wir wissen, dass wir einen Rückgabewert wie diesen erwarten können
# trellocli/models.py # module imports # dependencies imports # misc imports from typing import NamedTuple class GetOAuthTokenResponse(NamedTuple): token: str token_secret: str status_code: int
Beachten Sie andererseits widersprüchliche Namenskonventionen. Das py-trello
Modul hat beispielsweise eine Klasse namens List
. Eine Lösung hierfür wäre die Bereitstellung eines Alias beim Import.
# 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
Challenge Corner 💡 Kannst du versuchen, selbst mehr Modelle zu schreiben? Sehen Sie sich gerne an, um zu sehen, wie meine Modelle aussehen
Modelle runter, lasst uns offiziell mit dem Codieren des trelloservice
beginnen. Auch hier sollten wir uns auf die von uns erstellten Unit-Tests beziehen. Nehmen wir an, dass die aktuelle Testliste den Dienst nicht vollständig abdeckt. Kommen Sie immer zurück und fügen Sie bei Bedarf weitere Tests hinzu.
Fügen Sie wie üblich alle Importanweisungen oben ein. Erstellen Sie dann wie erwartet die TrelloService
Klasse und die Platzhaltermethoden. Die Idee ist, dass wir eine gemeinsame Instanz des Dienstes in cli.py
initialisieren und seine Methoden entsprechend aufrufen. Darüber hinaus streben wir nach Skalierbarkeit und damit nach einer umfassenden Abdeckung.
# 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
PS: Beachten Sie, dass unsere Tests dieses Mal bestanden werden, wenn wir unsere Tests durchführen. Tatsächlich wird uns das dabei helfen, sicherzustellen, dass wir auf dem richtigen Weg bleiben. Der Arbeitsablauf sollte darin bestehen, unsere Funktionen zu erweitern, unsere Tests auszuführen, auf Pass/Fail zu prüfen und entsprechend umzugestalten.
Beginnen wir mit der Funktion __init__
. Die Idee ist, hier die Funktion get_user_oauth_token
aufzurufen und den TrelloClient
zu initialisieren. Um die Notwendigkeit zu betonen, solche vertraulichen Informationen nur in der .env
Datei zu speichern, verwenden wir die Abhängigkeit python-dotenv
, um vertrauliche Informationen abzurufen. Nachdem wir unsere Datei pyproject.toml
entsprechend geändert haben, beginnen wir mit der Implementierung der Autorisierungsschritte.
# 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 )
In dieser Implementierung haben wir eine Hilfsmethode erstellt, um alle vorhersehbaren Fehler zu behandeln, z. B. wenn der Benutzer während der Autorisierung auf Deny
klickt. Darüber hinaus ist es so eingerichtet, dass es rekursiv nach der Autorisierung des Benutzers fragt, bis eine gültige Antwort zurückgegeben wird, denn Tatsache ist, dass wir nicht fortfahren können, es sei denn, der Benutzer autorisiert unsere App, auf seine Kontodaten zuzugreifen.
Challenge Corner 💡 Beachten Sie TRELLO_AUTHORIZATION_ERROR
? Können Sie diesen Fehler als Paketkonstante deklarieren? Weitere Informationen finden Sie unter „Setup“.
# 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 )
Zum Abrufen der Listen (Spalten) müssen wir die Board
Klasse von py-trello
auschecken, oder mit anderen Worten, wir müssen einen neuen Parameter des Board
Werttyps akzeptieren.
# 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 )
Challenge Corner 💡 Könnten Sie die Funktionen get_all_labels
und get_label
selbst implementieren? Überarbeiten Sie die Board
Klasse von py-trello
. Sehen Sie sich gerne an, um zu sehen, wie meine Implementierung aussieht
# 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 )
Glückwunsch! Du hast gewonnen. Nochmals spielen (j/n)?