paint-brush
Trello Pano Yönetimi için Python CLI Programı Nasıl Oluşturulur (Bölüm 2)ile@elainechan01
1,777 okumalar
1,777 okumalar

Trello Pano Yönetimi için Python CLI Programı Nasıl Oluşturulur (Bölüm 2)

ile Elaine Yun Ru Chan27m2023/11/07
Read on Terminal Reader

Çok uzun; Okumak

CLI komutları ve Python paket dağıtımı için iş mantığının nasıl yazılacağına odaklanan Trello Pano Yönetimi için Python CLI Programı Nasıl Oluşturulur konulu eğitim serisinin 2. Bölümü
featured image - Trello Pano Yönetimi için Python CLI Programı Nasıl Oluşturulur (Bölüm 2)
Elaine Yun Ru Chan HackerNoon profile picture
0-item

Şu ana kadar temel taş-kağıt-makas okul projesinin çok ötesindeyiz; hadi hemen konuya dalalım.


Bu eğitim sayesinde neyi başaracağız?

Trello Pano Yönetimi için Python CLI Programı Nasıl Oluşturulur (Bölüm 1) bölümünde, Trello SDK ile etkileşime girecek iş mantığını başarıyla oluşturduk.


İşte CLI programımızın mimarisinin kısa bir özeti:

Gereksinimlere dayalı CLI yapısının ayrıntılı tablo görünümü


Bu eğitimde, işlevsel ve işlevsel olmayan gereksinimlere odaklanarak projemizi bir CLI programına nasıl dönüştüreceğimize bakacağız.


Diğer yandan programımızı PyPI üzerinde paket olarak nasıl dağıtacağımızı da öğreneceğiz.


Başlayalım


Klasör Yapısı

Daha önce trelloservice modülümüzü barındıracak bir iskelet kurmayı başarmıştık. Bu sefer farklı işlevlere yönelik modüller içeren bir cli klasörü uygulamak istiyoruz:


  • yapılandırma
  • erişim
  • liste


Buradaki fikir, her komut grubunun komutlarının kendi modülünde saklanmasıdır. list komutuna gelince, herhangi bir komut grubuna ait olmadığından onu ana CLI dosyasında saklayacağız.


Öte yandan klasör yapımızı temizlemeye bakalım. Daha spesifik olarak, dizinlerin karmaşık olmadığından emin olarak yazılımın ölçeklenebilirliğini hesaba katmaya başlamalıyız.


İşte klasör yapımız hakkında bir öneri:

 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


assets klasörünün nasıl olduğuna dikkat ettiniz mi? Bu, README için ilgili varlıkları depolamak için kullanılacaktır, ancak trellocli yeni uygulanan bir shared klasör var, bunu yazılım genelinde kullanılacak modülleri depolamak için kullanacağız.


Kurmak

Giriş noktası dosyamız olan __main__.py değiştirerek başlayalım. İçe aktarmanın kendisine baktığımızda, ilgili modülleri kendi alt klasörlerinde saklamaya karar verdiğimiz için bu tür değişikliklere uyum sağlamamız gerekecek. Öte yandan, ana CLI modülünün ( cli.py ) çalıştırabileceğimiz bir app örneğine sahip olduğunu da varsayıyoruz.

 # 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()


cli.py dosyamıza hızlıca ilerleyin; app örneğimizi burada saklayacağız. Buradaki fikir, yazılım genelinde paylaşılacak bir Typer nesnesini başlatmaktır.

 # trellocli/cli/cli.py # module imports # dependencies imports from typer import Typer # misc imports # singleton instances app = Typer()


Bu konseptle ilerleyerek, komut satırı komut dosyalarımızı belirtmek için pyproject.toml dosyamızı değiştirelim. Burada paketimize bir isim vereceğiz ve giriş noktasını tanımlayacağız.

 # pyproject.toml [project.scripts] trellocli = "trellocli.__main__:main"


Yukarıdaki örneğe dayanarak, trellocli paket adı olarak tanımladık ve trellocli modülünde saklanan __main__ betiğindeki main işlev çalışma zamanı sırasında yürütülecek.


Artık yazılımımızın CLI kısmı ayarlandığına göre, CLI programımıza daha iyi hizmet verebilmek için trelloservice modülümüzü değiştirelim. Hatırlayacağınız gibi, trelloservice modülümüz, onaylanana kadar tekrar tekrar kullanıcıdan yetki isteyecek şekilde ayarlanmıştır. Bunu, yetki verilmediği takdirde programdan çıkacak ve kullanıcıyı config access komutunu çalıştırmaya teşvik edecek şekilde değiştireceğiz. Bu, programımızın talimatlar açısından daha temiz ve açıklayıcı olmasını sağlayacaktır.


Bunu kelimelere dökmek için şu işlevleri değiştireceğiz:


  • __init__
  • __load_oauth_token_env_var
  • authorize
  • is_authorized


__init__ işleviyle başlayarak, istemci kurulumunu burada yapmak yerine boş bir istemciyi başlatacağız.

 # trellocli/trelloservice.py class TrelloService: def __init__(self) -> None: self.__client = None


Mücadele Köşesi __load_oauth_token_env_var işlevimizi, yinelemeli olarak kullanıcının yetkilendirmesini istemeyecek şekilde değiştirebilir misiniz? İpucu: Özyinelemeli bir işlev, kendisini çağıran bir işlevdir.


authorize ve is_authorized yardımcı işlevlerine geçildiğinde, fikir, authorize istemciyi __load_oauth_token_env_var işlevini kullanarak ayarlama iş mantığını yürüteceği, is_authorized işlevinin ise yalnızca yetkilendirmenin verilip verilmediğine ilişkin bir boole değeri döndüreceğidir.

 # 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


__load_oauth_token_env_var ile authorize arasındaki farkın, __load_oauth_token_env_var , yetkilendirme belirtecini bir ortam değişkeni olarak depolamaya hizmet eden dahili bir işlev olduğunu, oysa authorize genel kullanıma açık bir işlev olduğunu anlayın; gerekli tüm kimlik bilgilerini almaya ve bir Trello İstemcisi'ni başlatmaya çalışır.


Mücadele Köşesi authorize işlevimizin AuthorizeResponse veri türünü nasıl döndürdüğüne dikkat edin. status_code niteliğine sahip bir model uygulayabilir misiniz? Trello Pano Yönetimi için Python CLI Programı Nasıl Oluşturulur bölümünün 1. Bölümüne bakın (İpucu: Modelleri nasıl oluşturduğumuza bakın)


Son olarak modülün altına doğru tekil bir TrelloService nesnesi başlatalım. Tam kodun nasıl göründüğünü görmek için bu yamaya başvurmaktan çekinmeyin: trello-cli-kit

 # trellocli/trelloservice.py trellojob = TrelloService()


Son olarak, program genelinde paylaşılacak bazı özel istisnaları başlatmak istiyoruz. Bu, başlatıcımızda tanımlanan ERRORS farklıdır, çünkü bu istisnalar BaseException alt sınıflarıdır ve tipik kullanıcı tanımlı istisnalar olarak hareket eder, oysa ERRORS daha çok 0'dan başlayan sabit değerler olarak hizmet eder.


İstisnalarımızı minimumda tutalım ve yaygın kullanım durumlarından bazılarını ele alalım; en önemlisi:


  • Okuma hatası: Trello'dan okuma hatası oluştuğunda ortaya çıkar
  • Yazma hatası: Trello'ya yazarken bir hata oluştuğunda ortaya çıkar
  • Yetkilendirme hatası: Trello için yetki verilmediğinde ortaya çıkar
  • Geçersiz kullanıcı girişi hatası: Kullanıcının CLI girişi tanınmadığında ortaya çıkar


 # trellocli/shared/custom_exceptions.py class TrelloReadError(BaseException): pass class TrelloWriteError(BaseException): pass class TrelloAuthorizationError(BaseException): pass class InvalidUserInputError(BaseException): pass


Birim Testleri

Bölüm I'de belirtildiği gibi, bu eğitimde Birim Testlerini kapsamlı bir şekilde ele almayacağız, o yüzden sadece gerekli öğelerle çalışalım:


  • Erişimi yapılandırmak için test edin
  • Trello kartını yapılandırmak için test edin
  • Yeni bir trello kartı oluşturmayı test edin
  • Trello panosu ayrıntılarını görüntülemek için test edin
  • Trello panosu ayrıntılarını görüntülemek için test edin (ayrıntılı görünüm)


Buradaki fikir, beklenen sonuçları test etmek için bir shell gibi bir komut satırı yorumlayıcısıyla alay etmektir. Typer modülünün harika yanı, kendi runner nesnesiyle birlikte gelmesidir. Testleri çalıştırmaya gelince, onu pytest modülüyle eşleştireceğiz. Daha fazla bilgi için Typer'ın resmi belgelerini görüntüleyin.


İlk testi, yani erişimi yapılandırmayı birlikte ele alalım. Fonksiyonun düzgün bir şekilde yürütülüp yürütülmediğini test ettiğimizi anlayın. Bunu yapmak için, sistem yanıtını ve çıkış kodunun success olup olmadığını, yani 0 olup olmadığını kontrol edeceğiz. İşte RedHat'ın çıkış kodlarının ne olduğu ve sistemin bunları iletişim süreçleri için nasıl kullandığı hakkında harika bir makalesi.

 # 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


Mücadele Köşesi 💡Artık ana fikri anladığınıza göre, diğer test senaryolarını kendi başınıza uygulayabilir misiniz? (İpucu: Arıza durumları için de test yapmayı düşünmelisiniz)


İş mantığı


Ana CLI Modülü

Bunun ana cli modülümüz olacağını anlayın - tüm komut grupları için (config, create), iş mantıkları daha iyi okunabilirlik için kendi ayrı dosyasında saklanacaktır.


Bu modülde list komutumuzu saklayacağız. Komutun derinliklerine indiğimizde aşağıdaki seçenekleri uygulamak istediğimizi biliyoruz:


  • board_name: config board önceden ayarlanmamışsa gereklidir
  • ayrıntılı: ayrıntılı görünümde görüntüleme


Board_name gerekli seçeneğinden başlayarak, bunu başarmanın birkaç yolu vardır; bunlardan biri geri çağırma işlevini kullanmak (Daha fazla bilgi için resmi belgeleri burada bulabilirsiniz) veya yalnızca varsayılan bir ortam değişkenini kullanmaktır. Ancak kullanım durumumuz açısından, koşullar karşılanmazsa InvalidUserInputError özel istisnamızı yükselterek konuyu basit tutalım.


Komutu oluşturmak için seçenekleri tanımlamayla başlayalım. Typer'da, resmi belgelerinde de belirtildiği gibi, bir seçeneği tanımlamanın temel bileşenleri şunlar olacaktır:


  • Veri tipi
  • Yardımcı metin
  • Varsayılan değer


Örneğin, aşağıdaki koşullarla detailed seçeneği oluşturmak için:


  • Veri türü: bool
  • Yardımcı metin: "Ayrıntılı görünümü etkinleştir"
  • Varsayılan değer: Yok


Kodumuz şöyle görünecek:

 detailed: Annotated[bool, typer.Option(help=”Enable detailed view)] = None


Genel olarak, list komutunu gerekli seçeneklerle tanımlamak için list bir Python İşlevi olarak ve seçeneklerini de gerekli parametreler olarak ele alacağız.

 # 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


Komutu, dosyanın üst kısmına doğru başlatılan app örneğine eklediğimizi unutmayın. Seçenekleri beğeninize göre değiştirmek için resmi Typer kod tabanında gezinmekten çekinmeyin.


Komutun iş akışına gelince, şunun gibi bir şey yapacağız:


  • Yetkilendirmeyi kontrol edin
  • Uygun panoyu kullanacak şekilde yapılandırın ( board_name seçeneğinin sağlanıp sağlanmadığını kontrol edin)
  • Trello panosunu okunacak şekilde ayarlayın
  • Uygun Trello kartı verilerini alın ve Trello listesine göre kategorilere ayırın
  • Verileri görüntüle ( detailed seçeneğin seçilip seçilmediğini kontrol edin)


Dikkat edilmesi gereken birkaç nokta…


  • Trellojob SUCCESS dışında bir durum kodu ürettiğinde İstisnaları gündeme getirmek istiyoruz. try-catch bloklarını kullanarak programımızın ölümcül çökmelerinin önüne geçebiliriz.
  • Kullanılacak uygun panoyu yapılandırırken, alınan board_id değerine göre Trello panosunu kullanım için ayarlamaya çalışacağız. Bu nedenle aşağıdaki kullanım durumlarını ele almak istiyoruz
    • Trellojob'taki get_all_boards işlevi kullanılarak bir eşleşme kontrol edilerek board_name açıkça sağlandıysa board_id alınması
    • board_name seçeneği kullanılmadıysa board_id ortam değişkeni olarak depolandığı şekilde alma
  • Göstereceğimiz veriler, rich paketteki Table işlevi kullanılarak biçimlendirilecektir. rich hakkında daha fazla bilgi için lütfen resmi belgelerine bakın.
    • Ayrıntılı: Trello listelerinin sayısının, kart sayısının ve tanımlanmış etiketlerin bir özetini görüntüleyin. Her Trello listesi için tüm kartları ve bunlara karşılık gelen adları, açıklamaları ve ilgili etiketleri görüntüleyin
    • Ayrıntılı olmayan: Trello listelerinin sayısı, kart sayısı ve tanımlı etiketlerin özetini görüntüleyin


Her şeyi bir araya getirdiğimizde aşağıdaki gibi bir şey elde ederiz. Yasal Uyarı: TrelloService henüz uygulamadığımız bazı eksik işlevler olabilir. Bunları uygulamak için yardıma ihtiyacınız varsa lütfen bu yamaya bakın: 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...")


Yazılımımızı çalışırken görmek için terminalde python -m trellocli --help çalıştırmanız yeterlidir. Varsayılan olarak Typer modülü --help komutunun çıktısını kendi başına dolduracaktır. Paket adı olarak trellocli nasıl çağırabildiğimize dikkat edin; bunun daha önce pyproject.toml dosyamızda nasıl tanımlandığını hatırlıyor musunuz?


Biraz ileri saralım ve create ve config komut gruplarını da başlatalım. Bunu yapmak için app nesnemizde add_typer işlevini kullanacağız. Buradaki fikir, komut grubunun kendi app nesnesine sahip olmasıdır ve biz bunu, komut grubunun adı ve yardımcı metinle birlikte cli.py dosyasındaki ana app ekleyeceğiz. Bunun gibi bir şeye benzemeli

 # trellocli/cli/cli.py app.add_typer(cli_config.app, name="config", help="COMMAND GROUP to initialize configurations")


Mücadele Köşesi 💡Oluşturma create grubunu kendi başınıza içe aktarabilir misiniz? Yardım için bu yamaya başvurmaktan çekinmeyin: trello-cli-kit


Alt komutlar

create için bir komut grubu oluşturmak için ilgili komutları kendi modülünde saklayacağız. Kurulum, Typer nesnesini başlatma ihtiyacı nedeniyle cli.py benzer. Komutlara gelince, özel istisnaların kullanılması ihtiyacına da uymak isteriz. Ele almak istediğimiz ek bir konu, kullanıcının Ctrl + C tuşlarına basması veya başka bir deyişle işlemi kesintiye uğratmasıdır. Bunu list komutumuz için ele almamamızın nedeni, buradaki farkın, config komut grubunun etkileşimli komutlardan oluşmasıdır. Etkileşimli komutlar arasındaki temel fark, sürekli kullanıcı etkileşimi gerektirmeleridir. Tabii ki, doğrudan komutumuzu yürütmenin uzun zaman aldığını varsayalım. Ayrıca olası klavye kesintilerini ele almak da en iyi uygulamadır.


access komutuyla başlayarak, sonunda TrelloService oluşturulan authorize işlevini kullanacağız. authorize işlevi konfigürasyonu tek başına idare ettiğinden, yalnızca sürecin yürütüldüğünü doğrulamamız gerekecek.

 # 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...")


board komutuna gelince, iyi bir kullanıcı deneyimi sağlamak için, kullanıcı etkileşimi için bir terminal GUI'sini görüntülemek üzere Basit Terminal Menüsü de dahil olmak üzere çeşitli modüller kullanacağız. Ana fikir şu şekildedir:


  • Yetkilendirmeyi kontrol edin
  • Kullanıcının hesabından tüm Trello panolarını alın
  • Trello panolarının tek seçimli terminal menüsünü görüntüleyin
  • Seçilen Trello kartı kimliğini ortam değişkeni olarak ayarlayın


 # 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...")


Son olarak yazılımımızın temel işlevsel gereksinimine geçiyoruz: Trello panosundaki listeye yeni bir kart ekleme. list komutumuzdan panodan veri alana kadar aynı adımları kullanacağız.


Bunun dışında, yeni kartı doğru şekilde yapılandırmak için etkileşimli olarak kullanıcı girişi isteyeceğiz:


  • Eklenecek Trello listesi: Tek seçim
  • Kart adı: Metin
  • [İsteğe bağlı] Kart açıklaması: Metin
  • [İsteğe bağlı] Etiketler: Çoklu seçim
  • Onay: evet/H


Kullanıcının bir listeden seçim yapmasını gerektiren tüm istemler için, daha önce olduğu gibi Simple Terminal Menu paketini kullanacağız. Metin girişi veya kullanıcının onayı gibi diğer istemler ve çeşitli öğeler için, yalnızca rich paketi kullanacağız. İsteğe bağlı değerleri doğru bir şekilde ele almamız gerektiğini de unutmamak önemlidir:


  • Kullanıcılar açıklama sağlamayı atlayabilir
  • Kullanıcılar Etiketler için boş bir seçim sağlayabilir


 # 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...")


Mücadele Köşesi 💡Ekleme add için bir ilerleme çubuğu görüntüleyebilir misiniz? İpucu: rich durum özelliğini kullanmaya bir göz atın


Paket Dağıtımı

İşte işin eğlenceli kısmı geliyor; yazılımımızı resmi olarak PyPI üzerinde dağıtıyoruz. Bunu yapmak için şu boru hattını takip edeceğiz:


  • Meta verileri yapılandır + README'yi güncelle
  • PyPI'yi Test Etmeye Yükle
  • GitHub Eylemlerini Yapılandırma
  • Kodu v1.0.0 Etiketine aktarın
  • Kodu PyPI'ye dağıtın 🎉


Ayrıntılı bir açıklama için Ramit Mittal'ın Python Packaging hakkındaki bu harika eğitimine göz atın.


Meta Veri Yapılandırması

pyproject.toml dosyamız için ihtiyacımız olan son detay, paketin kendisini hangi modülün sakladığını belirtmektir. Bizim durumumuzda bu trellocli olacak. Eklenecek meta veriler şunlardır:

 # pyproject.toml [tool.setuptools] packages = ["trellocli"]


README.md dosyamıza gelince, kullanım yönergeleri veya nasıl başlanacağı konusunda bir tür kılavuz sağlamak harika bir uygulamadır. README.md dosyanıza görseller eklediyseniz, mutlak URL'sini kullanmalısınız; bu genellikle aşağıdaki formattadır

 https://raw.githubusercontent.com/<user>/<repo>/<branch>/<path-to-image>


TestPyPI

Paketimizi oluşturmak ve yayınlamak için build ve twine araçlarını kullanacağız. Paketiniz için bir kaynak arşivi ve tekerlek oluşturmak üzere terminalinizde aşağıdaki komutu çalıştırın:

 python -m build


TestPyPI'de zaten bir hesabınızın kurulu olduğundan emin olun ve aşağıdaki komutu çalıştırın

 twine upload -r testpypi dist/*


Kullanıcı adınızı ve şifrenizi yazmanız istenecektir. İki faktörlü kimlik doğrulamanın etkin olması nedeniyle, bir API Jetonu kullanmanız gerekecektir (TestPyPI API jetonunun nasıl edinileceği hakkında daha fazla bilgi için: belgelere bağlantı ). Basitçe aşağıdaki değerleri girin:


  • Kullanıcı adı: jeton
  • şifre: <TestPyPI belirteciniz>


Bu tamamlandıktan sonra yeni dağıtılan paketinizi kontrol etmek için TestPyPI'ye gidebilmelisiniz!


GitHub Kurulumu

Amaç, GitHub'u paketinizin yeni sürümlerini etiketlere dayalı olarak sürekli güncellemenin bir yolu olarak kullanmaktır.


Öncelikle GitHub iş akışınızdaki Actions sekmesine gidin ve yeni bir iş akışı seçin. GitHub Actions tarafından oluşturulan Publish Python Package iş akışını kullanacağız. İş akışının depo sırlarından okumayı nasıl gerektirdiğine dikkat edin. PyPI jetonunuzu belirtilen ad altında sakladığınızdan emin olun (PyPI API jetonunun alınması TestPyPI'ninkine benzer).


İş akışı oluşturulduktan sonra kodumuzu v1.0.0'ı etiketlemeye zorlayacağız. Sürüm adlandırma sözdizimi hakkında daha fazla bilgi için Py-Pkgs'in harika bir açıklaması burada: belgelere bağlantı


Her zamanki pull , add ve commit komutlarını çalıştırmanız yeterlidir. Ardından, aşağıdaki komutu çalıştırarak en son işleminiz için bir etiket oluşturun (Etiketler hakkında daha fazla bilgi için: belgeye bağlantı )

 git tag <tagname> HEAD


Son olarak yeni etiketinizi uzak depoya gönderin

 git push <remote name> <tag name>


Daha fazla bilgi edinmek istiyorsanız Karol Horosin'in CI/CD'yi Python Paketinizle Entegrasyon hakkındaki harika makalesini burada bulabilirsiniz. Ama şimdilik arkanıza yaslanın ve son başarınızın tadını çıkarın 🎉. Paketinizi PyPI'ye dağıtırken GitHub Eylemleri iş akışı olarak sihrin ortaya çıkmasını izlemekten çekinmeyin.


Sarmak

Bu uzun bir konuydu 😓. Bu eğitim sayesinde Typer modülünü kullanarak yazılımınızı bir CLI programına dönüştürmeyi ve paketinizi PyPI'ye dağıtmayı öğrendiniz. Daha derine inmek için komutları ve komut gruplarını tanımlamayı, etkileşimli bir CLI oturumu geliştirmeyi ve klavye kesintisi gibi yaygın CLI senaryolarıyla uğraşmayı öğrendiniz.


Her şeyin üstesinden gelme konusunda tam bir sihirbaz oldun. İsteğe bağlı işlevleri uygulayacağımız 3. Bölümde bana katılmaz mısınız?