paint-brush
ट्रेलो बोर्ड प्रबंधन के लिए पायथन सीएलआई प्रोग्राम कैसे बनाएं (भाग 2)द्वारा@elainechan01
1,777 रीडिंग
1,777 रीडिंग

ट्रेलो बोर्ड प्रबंधन के लिए पायथन सीएलआई प्रोग्राम कैसे बनाएं (भाग 2)

द्वारा Elaine Yun Ru Chan27m2023/11/07
Read on Terminal Reader

बहुत लंबा; पढ़ने के लिए

ट्रेलो बोर्ड प्रबंधन के लिए पायथन सीएलआई प्रोग्राम कैसे बनाएं, इस पर ट्यूटोरियल श्रृंखला का भाग 2, सीएलआई कमांड और पायथन पैकेज वितरण के लिए व्यावसायिक तर्क लिखने के तरीके पर ध्यान केंद्रित करता है।
featured image - ट्रेलो बोर्ड प्रबंधन के लिए पायथन सीएलआई प्रोग्राम कैसे बनाएं (भाग 2)
Elaine Yun Ru Chan HackerNoon profile picture
0-item

अब तक हम मुख्य रॉक-पेपर-कैंची स्कूल प्रोजेक्ट से बहुत आगे निकल चुके हैं - आइए सीधे इसमें उतरें।


इस ट्यूटोरियल के माध्यम से हम क्या हासिल करेंगे?

ट्रेलो बोर्ड प्रबंधन के लिए पायथन सीएलआई प्रोग्राम कैसे बनाएं (भाग 1) में, हमने ट्रेलो एसडीके के साथ बातचीत करने के लिए सफलतापूर्वक व्यावसायिक तर्क बनाया।


यहां हमारे सीएलआई कार्यक्रम की संरचना का एक त्वरित सारांश दिया गया है:

आवश्यकताओं के आधार पर सीएलआई संरचना का विस्तृत तालिका दृश्य


इस ट्यूटोरियल में, हम कार्यात्मक और गैर-कार्यात्मक आवश्यकताओं पर ध्यान केंद्रित करते हुए, अपने प्रोजेक्ट को सीएलआई प्रोग्राम में बदलने का तरीका देखेंगे।


दूसरी ओर, हम यह भी सीखेंगे कि अपने प्रोग्राम को PyPI पर एक पैकेज के रूप में कैसे वितरित किया जाए।


आएँ शुरू करें


फ़ोल्डर संरचना

पहले, हम अपने trelloservice मॉड्यूल को होस्ट करने के लिए एक कंकाल स्थापित करने में कामयाब रहे। इस बार, हम विभिन्न कार्यात्मकताओं के लिए मॉड्यूल के साथ एक cli फ़ोल्डर लागू करना चाहते हैं, अर्थात्:


  • कॉन्फ़िग
  • पहुँच
  • सूची


विचार यह है कि, प्रत्येक कमांड समूह के लिए, उसके कमांड को उसके अपने मॉड्यूल में संग्रहीत किया जाएगा। जहां तक list कमांड का सवाल है, हम इसे मुख्य सीएलआई फ़ाइल में संग्रहीत करेंगे क्योंकि यह किसी कमांड समूह से संबंधित नहीं है।


दूसरी ओर, आइए अपनी फ़ोल्डर संरचना को साफ करने पर गौर करें। अधिक विशेष रूप से, हमें यह सुनिश्चित करके सॉफ़्टवेयर की स्केलेबिलिटी का हिसाब-किताब रखना शुरू करना चाहिए कि निर्देशिकाएँ अव्यवस्थित न हों।


यहां हमारी फ़ोल्डर संरचना पर एक सुझाव दिया गया है:

 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 फ़ोल्डर कैसा है? इसका उपयोग हमारे README के लिए संबंधित परिसंपत्तियों को संग्रहीत करने के लिए किया जाएगा, जबकि trellocli में एक नव-कार्यान्वित shared फ़ोल्डर होने के कारण, हम इसका उपयोग सॉफ़्टवेयर में उपयोग किए जाने वाले मॉड्यूल को संग्रहीत करने के लिए करेंगे।


स्थापित करना

आइए अपनी प्रविष्टि बिंदु फ़ाइल, __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()


हमारी cli.py फ़ाइल पर तेजी से आगे बढ़ें; हम यहां अपना app इंस्टेंस संग्रहीत करेंगे। विचार यह है कि सॉफ़्टवेयर में साझा करने के लिए एक Typer ऑब्जेक्ट को प्रारंभ किया जाए।

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


इस अवधारणा के साथ आगे बढ़ते हुए, आइए अपनी कमांड लाइन स्क्रिप्ट को निर्दिष्ट करने के लिए अपने pyproject.toml संशोधित करें। यहां, हम अपने पैकेज के लिए एक नाम प्रदान करेंगे और प्रवेश बिंदु को परिभाषित करेंगे।

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


उपरोक्त नमूने के आधार पर, हमने trellocli पैकेज नाम के रूप में परिभाषित किया है और __main__ स्क्रिप्ट में main फ़ंक्शन, जो trellocli मॉड्यूल में संग्रहीत है, रनटाइम के दौरान निष्पादित किया जाएगा।


अब जब हमारे सॉफ़्टवेयर का सीएलआई भाग सेट हो गया है, तो आइए अपने सीएलआई प्रोग्राम को बेहतर ढंग से सेवा प्रदान करने के लिए अपने trelloservice मॉड्यूल को संशोधित करें। जैसा कि आप याद करते हैं, हमारा trelloservice मॉड्यूल स्वीकृत होने तक उपयोगकर्ता से बार-बार प्राधिकरण मांगने के लिए सेट किया गया है। हम इसे इस तरह से संशोधित करेंगे कि यदि प्राधिकरण नहीं दिया गया है तो प्रोग्राम बाहर निकल जाएगा और उपयोगकर्ता से config access कमांड चलाने का आग्रह करेगा। इससे यह सुनिश्चित होगा कि हमारा कार्यक्रम निर्देशों के संदर्भ में स्वच्छ और अधिक वर्णनात्मक है।


इसे शब्दों में कहें तो, हम इन कार्यों को संशोधित करेंगे:


  • __init__
  • __load_oauth_token_env_var
  • authorize
  • is_authorized


__init__ फ़ंक्शन से शुरू करके, हम यहां क्लाइंट सेटअप को संभालने के बजाय एक खाली क्लाइंट को प्रारंभ करेंगे।

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


चैलेंज कॉर्नर 💡क्या आप हमारे __load_oauth_token_env_var फ़ंक्शन को संशोधित कर सकते हैं ताकि यह उपयोगकर्ता के प्राधिकरण के लिए बार-बार संकेत न दे? संकेत: एक पुनरावर्ती फ़ंक्शन एक ऐसा फ़ंक्शन है जो स्वयं को कॉल करता है।


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


समझें कि __load_oauth_token_env_var और authorize के बीच का अंतर यह है कि __load_oauth_token_env_var एक आंतरिक फेसिंग फ़ंक्शन है जो ऑथराइज़ेशन टोकन को एक पर्यावरण चर के रूप में संग्रहीत करने का कार्य करता है जबकि authorize सार्वजनिक रूप से फेसिंग फ़ंक्शन होने के कारण, यह सभी आवश्यक क्रेडेंशियल्स को पुनः प्राप्त करने और एक ट्रेलो क्लाइंट को आरंभ करने का प्रयास करता है।


चैलेंज कॉर्नर 💡ध्यान दें कि हमारा authorize फ़ंक्शन AuthorizeResponse डेटा प्रकार कैसे लौटाता है। क्या आप ऐसा मॉडल लागू कर सकते हैं जिसमें status_code विशेषता हो? ट्रेलो बोर्ड प्रबंधन के लिए पायथन सीएलआई प्रोग्राम कैसे बनाएं के भाग 1 का संदर्भ लें (संकेत: देखें कि हमने मॉडल कैसे बनाए)


अंत में, आइए मॉड्यूल के नीचे की ओर एक सिंगलटन TrelloService ऑब्जेक्ट को इंस्टेंट करें। पूरा कोड कैसा दिखता है यह देखने के लिए बेझिझक इस पैच को देखें: ट्रेलो-क्ली-किट

 # trellocli/trelloservice.py trellojob = TrelloService()


अंत में, हम प्रोग्राम में साझा किए जाने वाले कुछ कस्टम अपवादों को प्रारंभ करना चाहते हैं। यह हमारे इनिशियलाइज़र में परिभाषित ERRORS से भिन्न है क्योंकि ये अपवाद BaseException के उपवर्ग हैं और विशिष्ट उपयोगकर्ता-परिभाषित अपवादों के रूप में कार्य करते हैं, जबकि ERRORS 0 से शुरू होने वाले स्थिर मानों के रूप में अधिक कार्य करते हैं।


आइए अपने अपवादों को न्यूनतम रखें और कुछ सामान्य उपयोग के मामलों को अपनाएं, विशेष रूप से:


  • त्रुटि पढ़ें: ट्रेलो से पढ़ने में त्रुटि होने पर उठाया जाता है
  • त्रुटि लिखें: ट्रेलो को लिखने में त्रुटि होने पर इसे उठाया जाता है
  • प्राधिकरण त्रुटि: जब ट्रेलो के लिए प्राधिकरण प्रदान नहीं किया जाता है तो उठाया जाता है
  • अमान्य उपयोगकर्ता इनपुट त्रुटि: तब उठाया जाता है जब उपयोगकर्ता का सीएलआई इनपुट पहचाना नहीं जाता है


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


यूनिट परीक्षण

जैसा कि भाग I में बताया गया है, हम इस ट्यूटोरियल में यूनिट टेस्ट को व्यापक रूप से कवर नहीं करेंगे, तो आइए केवल आवश्यक तत्वों के साथ काम करें:


  • पहुंच कॉन्फ़िगर करने के लिए परीक्षण करें
  • ट्रेलो बोर्ड को कॉन्फ़िगर करने के लिए परीक्षण करें
  • नया ट्रेलो कार्ड बनाने के लिए परीक्षण करें
  • ट्रेलो बोर्ड विवरण प्रदर्शित करने के लिए परीक्षण करें
  • ट्रेलो बोर्ड विवरण प्रदर्शित करने के लिए परीक्षण करें (विस्तृत दृश्य)


विचार अपेक्षित परिणामों के परीक्षण के लिए एक shell की तरह एक कमांड लाइन दुभाषिया का नकल करना है। Typer मॉड्यूल के बारे में सबसे अच्छी बात यह है कि यह अपने स्वयं के runner ऑब्जेक्ट के साथ आता है। जहां तक परीक्षण चलाने की बात है, हम इसे pytest मॉड्यूल के साथ जोड़ देंगे। अधिक जानकारी के लिए, टाइपर द्वारा आधिकारिक दस्तावेज़ देखें।


आइए पहले परीक्षण पर एक साथ काम करें, यानी पहुंच को कॉन्फ़िगर करें। समझें कि हम परीक्षण कर रहे हैं कि फ़ंक्शन ठीक से निष्पादित होता है या नहीं। ऐसा करने के लिए, हम सिस्टम प्रतिक्रिया की जांच करेंगे और देखेंगे कि क्या निकास कोड success है या नहीं। यहां रेडहैट का एक बेहतरीन लेख है कि निकास कोड क्या हैं और सिस्टम प्रक्रियाओं को संचार करने के लिए उनका उपयोग कैसे करता है।

 # 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


चैलेंज कॉर्नर 💡अब जब आपको इसका सार मिल गया है, तो क्या आप अन्य परीक्षण मामलों को स्वयं लागू कर सकते हैं? (संकेत: आपको विफलता के मामलों के परीक्षण पर भी विचार करना चाहिए)


व्यापार का तर्क


मुख्य सीएलआई मॉड्यूल

समझें कि यह हमारा मुख्य cli मॉड्यूल होगा - सभी कमांड समूहों (कॉन्फ़िगरेशन, क्रिएट) के लिए, बेहतर पठनीयता के लिए उनके व्यावसायिक तर्क को अपनी अलग फ़ाइल में संग्रहीत किया जाएगा।


इस मॉड्यूल में, हम अपनी list कमांड संग्रहीत करेंगे। कमांड में गहराई से जाने पर, हम जानते हैं कि हम निम्नलिखित विकल्पों को लागू करना चाहते हैं:


  • बोर्ड_नाम: यदि config board पहले सेट नहीं किया गया था तो आवश्यक है
  • विस्तृत: विस्तृत दृश्य में प्रदर्शित करें


बोर्ड_नाम आवश्यक विकल्प से शुरू करके, इसे प्राप्त करने के कुछ तरीके हैं, उनमें से एक कॉलबैक फ़ंक्शन का उपयोग करना है (अधिक जानकारी के लिए, यहां आधिकारिक दस्तावेज़ हैं) या बस एक डिफ़ॉल्ट पर्यावरण चर का उपयोग करना है। हालाँकि, हमारे उपयोग के मामले में, यदि शर्तें पूरी नहीं होती हैं, तो आइए अपने InvalidUserInputError कस्टम अपवाद को बढ़ाकर इसे सीधा रखें।


कमांड बनाने के लिए, आइए विकल्पों को परिभाषित करने से शुरुआत करें। टाइपर में, जैसा कि उनके आधिकारिक दस्तावेज़ों में बताया गया है, किसी विकल्प को परिभाषित करने के लिए मुख्य तत्व होंगे:


  • डेटा प्रकार
  • सहायक पाठ
  • डिफ़ॉल्ट मान


उदाहरण के लिए, निम्नलिखित शर्तों के साथ detailed विकल्प बनाने के लिए:


  • डेटा प्रकार: बूल
  • सहायक पाठ: "विस्तृत दृश्य सक्षम करें"
  • डिफ़ॉल्ट मान: कोई नहीं


हमारा कोड इस तरह दिखेगा:

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


कुल मिलाकर, आवश्यक विकल्पों के साथ 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


ध्यान दें कि हम फ़ाइल के शीर्ष पर प्रारंभ किए गए app इंस्टेंस में कमांड जोड़ रहे हैं। अपनी पसंद के अनुसार विकल्पों को संशोधित करने के लिए आधिकारिक टाइपर कोडबेस के माध्यम से नेविगेट करने में संकोच न करें।


जहां तक कमांड के वर्कफ़्लो का सवाल है, हम कुछ इस तरह से काम कर रहे हैं:


  • प्राधिकरण की जाँच करें
  • उपयुक्त बोर्ड का उपयोग करने के लिए कॉन्फ़िगर करें (जांचें कि क्या board_name विकल्प प्रदान किया गया था)
  • पढ़ने के लिए ट्रेलो बोर्ड स्थापित करें
  • उपयुक्त ट्रेलो कार्ड डेटा पुनर्प्राप्त करें और ट्रेलो सूची के आधार पर वर्गीकृत करें
  • डेटा प्रदर्शित करें (जांचें कि क्या detailed विकल्प चुना गया था)


ध्यान देने योग्य कुछ बातें...


  • जब ट्रेलोजॉब SUCCESS के अलावा कोई अन्य स्टेटस कोड उत्पन्न करता है तो हम अपवाद बढ़ाना चाहते हैं। try-catch ब्लॉक का उपयोग करके, हम अपने प्रोग्राम को घातक क्रैश से बचा सकते हैं।
  • उपयोग के लिए उपयुक्त बोर्ड को कॉन्फ़िगर करते समय, हम पुनर्प्राप्त board_id के आधार पर उपयोग के लिए ट्रेलो बोर्ड स्थापित करने का प्रयास करेंगे। इस प्रकार, हम निम्नलिखित उपयोग के मामलों को कवर करना चाहते हैं
    • यदि board_id स्पष्ट रूप से प्रदान किया गया था, तो ट्रेलोजॉब में get_all_boards फ़ंक्शन board_name उपयोग करके मिलान की जांच करके बोर्ड_आईडी को पुनः प्राप्त करना
    • यदि board_name विकल्प का उपयोग नहीं किया गया था, तो पर्यावरण चर के रूप में संग्रहीत board_id पुनः प्राप्त करना
  • हम जो डेटा प्रदर्शित करेंगे उसे rich पैकेज से Table कार्यक्षमता का उपयोग करके स्वरूपित किया जाएगा। rich के बारे में अधिक जानकारी के लिए कृपया उनके आधिकारिक दस्तावेज़ देखें
    • विस्तृत: ट्रेलो सूचियों की संख्या, कार्डों की संख्या और परिभाषित लेबल का सारांश प्रदर्शित करें। प्रत्येक ट्रेलो सूची के लिए, सभी कार्ड और उनके संबंधित नाम, विवरण और संबंधित लेबल प्रदर्शित करें
    • गैर-विस्तृत: ट्रेलो सूचियों की संख्या, कार्डों की संख्या और परिभाषित लेबल का सारांश प्रदर्शित करें


सब कुछ एक साथ रखने पर हमें कुछ इस प्रकार मिलता है। अस्वीकरण: TrelloService के कुछ फ़ंक्शन गायब हो सकते हैं जिन्हें हमने अभी तक लागू नहीं किया है। यदि आपको उन्हें लागू करने में सहायता की आवश्यकता है तो कृपया इस पैच को देखें: ट्रेलो-क्ली-किट

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


हमारे सॉफ़्टवेयर को क्रियाशील देखने के लिए, बस टर्मिनल में python -m trellocli --help चलाएँ। डिफ़ॉल्ट रूप से, टाइपर मॉड्यूल --help कमांड के लिए आउटपुट को अपने आप पॉप्युलेट कर देगा। और ध्यान दें कि हम trellocli पैकेज नाम के रूप में कैसे कॉल कर सकते हैं - याद रखें कि इसे पहले हमारे pyproject.toml में कैसे परिभाषित किया गया था?


आइए थोड़ा तेजी से आगे बढ़ें और create और config कमांड ग्रुप को भी इनिशियलाइज़ करें। ऐसा करने के लिए, हम बस अपने app ऑब्जेक्ट पर add_typer फ़ंक्शन का उपयोग करेंगे। विचार यह है कि कमांड समूह का अपना app ऑब्जेक्ट होगा, और हम उसे कमांड समूह के नाम और सहायक टेक्स्ट के साथ cli.py में मुख्य app में जोड़ देंगे। यह कुछ इस तरह दिखना चाहिए

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


चैलेंज कॉर्नर 💡क्या आप create कमांड ग्रुप को स्वयं आयात कर सकते हैं? मदद के लिए बेझिझक इस पैच को देखें: ट्रेलो-क्ली-किट


उपआदेश

create के लिए एक कमांड समूह स्थापित करने के लिए, हम इसके संबंधित कमांड को अपने मॉड्यूल में संग्रहीत करेंगे। यह सेटअप cli.py के समान है जिसमें टाइपर ऑब्जेक्ट को तुरंत चालू करने की आवश्यकता होती है। जहां तक आदेशों का सवाल है, हम कस्टम अपवादों का उपयोग करने की आवश्यकता का भी पालन करना चाहेंगे। एक अतिरिक्त विषय जिसे हम कवर करना चाहते हैं वह है जब उपयोगकर्ता Ctrl + C दबाता है, या दूसरे शब्दों में, प्रक्रिया को बाधित करता है। हमने इसे अपनी list कमांड के लिए कवर नहीं किया, इसका कारण यह है कि यहां अंतर यह है कि config कमांड समूह में इंटरैक्टिव कमांड होते हैं। इंटरैक्टिव कमांड के बीच मुख्य अंतर यह है कि उन्हें निरंतर उपयोगकर्ता इंटरैक्शन की आवश्यकता होती है। बेशक, यह कहें कि हमारी सीधी कमांड को निष्पादित होने में लंबा समय लगता है। संभावित कीबोर्ड व्यवधानों को संभालना भी सर्वोत्तम अभ्यास है।


access कमांड से शुरू करके, हम अंततः हमारे TrelloService में बनाए गए authorize फ़ंक्शन का उपयोग करेंगे। चूंकि 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...")


जहां तक board कमांड का सवाल है, हम एक अच्छा उपयोगकर्ता अनुभव प्रदान करने के लिए विभिन्न मॉड्यूल का उपयोग करेंगे, जिसमें उपयोगकर्ता इंटरैक्शन के लिए टर्मिनल जीयूआई प्रदर्शित करने के लिए सरल टर्मिनल मेनू भी शामिल है। मुख्य विचार इस प्रकार है:


  • प्राधिकरण के लिए जाँच करें
  • उपयोगकर्ता के खाते से सभी ट्रेलो बोर्ड पुनर्प्राप्त करें
  • ट्रेलो बोर्डों का एकल-चयन टर्मिनल मेनू प्रदर्शित करें
  • चयनित ट्रेलो बोर्ड आईडी को पर्यावरण चर के रूप में सेट करें


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


अंत में, हम अपने सॉफ़्टवेयर की मुख्य कार्यात्मक आवश्यकता की ओर बढ़ रहे हैं - ट्रेलो बोर्ड पर एक सूची में एक नया कार्ड जोड़ें। हम बोर्ड से डेटा पुनर्प्राप्त करने तक अपनी list कमांड से समान चरणों का उपयोग करेंगे।


इसके अलावा, हम नए कार्ड को ठीक से कॉन्फ़िगर करने के लिए उपयोगकर्ता इनपुट के लिए इंटरैक्टिव रूप से अनुरोध करेंगे:


  • इसमें जोड़ी जाने वाली ट्रेलो सूची: एकल-चयन
  • कार्ड का नाम: पाठ
  • [वैकल्पिक] कार्ड विवरण: पाठ
  • [वैकल्पिक] लेबल: बहु-चयन
  • पुष्टिकरण: हाँ/नहीं


उपयोगकर्ता को किसी सूची से चयन करने के लिए आवश्यक सभी संकेतों के लिए, हम पहले की तरह Simple Terminal Menu पैकेज का उपयोग करेंगे। जहां तक अन्य संकेतों और विविध वस्तुओं जैसे टेक्स्ट इनपुट या उपयोगकर्ता की पुष्टि की आवश्यकता है, हम केवल rich पैकेज का उपयोग करेंगे। यह ध्यान रखना भी महत्वपूर्ण है कि हमें वैकल्पिक मानों को ठीक से संभालना होगा:


  • उपयोगकर्ता विवरण प्रदान करना छोड़ सकते हैं
  • उपयोगकर्ता लेबल के लिए एक खाली चयन प्रदान कर सकते हैं


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


चैलेंज कॉर्नर 💡क्या आप add प्रक्रिया के लिए प्रगति बार प्रदर्शित कर सकते हैं? संकेत: rich की स्थिति सुविधा का उपयोग करने पर एक नज़र डालें


पैकेज वितरण

यहाँ मज़ेदार हिस्सा आता है - आधिकारिक तौर पर हमारे सॉफ़्टवेयर को PyPI पर वितरित करना। ऐसा करने के लिए हम इस पाइपलाइन का अनुसरण करेंगे:


  • मेटाडेटा कॉन्फ़िगर करें + README अपडेट करें
  • PyPI का परीक्षण करने के लिए अपलोड करें
  • GitHub क्रियाएँ कॉन्फ़िगर करें
  • कोड को टैग v1.0.0 पर पुश करें
  • PyPI को कोड वितरित करें 🎉


विस्तृत विवरण के लिए, रामित मित्तल द्वारा पायथन पैकेजिंग पर इस महान ट्यूटोरियल को देखें।


मेटाडेटा कॉन्फ़िगरेशन

हमारे pyproject.toml के लिए हमें जो अंतिम विवरण चाहिए वह यह निर्दिष्ट करना है कि कौन सा मॉड्यूल पैकेज को स्वयं संग्रहीत करता है। हमारे मामले में, वह trellocli होगा। यहां जोड़ने के लिए मेटाडेटा है:

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


जहां तक हमारे README.md का सवाल है, किसी प्रकार की मार्गदर्शिका प्रदान करना बहुत अच्छा अभ्यास है, चाहे वह उपयोग दिशानिर्देश हो या शुरुआत कैसे करें। यदि आपने अपने README.md में छवियां शामिल की हैं, तो आपको इसके पूर्ण URL का उपयोग करना चाहिए, जो आमतौर पर निम्न प्रारूप का होता है

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


TestPyPI

हम अपना पैकेज बनाने और प्रकाशित करने के लिए build और twine टूल का उपयोग करेंगे। अपने पैकेज के लिए स्रोत संग्रह और एक व्हील बनाने के लिए अपने टर्मिनल में निम्नलिखित कमांड चलाएँ:

 python -m build


सुनिश्चित करें कि आपने TestPyPI पर पहले से ही एक खाता स्थापित कर लिया है, और निम्न आदेश चलाएँ

 twine upload -r testpypi dist/*


आपको अपना उपयोगकर्ता नाम और पासवर्ड टाइप करने के लिए कहा जाएगा। दो कारक प्रमाणीकरण सक्षम होने के कारण, आपको एक एपीआई टोकन का उपयोग करने की आवश्यकता होगी (टेस्टपीपीआई एपीआई टोकन प्राप्त करने के तरीके के बारे में अधिक जानकारी के लिए: दस्तावेज़ से लिंक करें )। बस निम्नलिखित मान डालें:


  • उपयोगकर्ता नाम: टोकन
  • पासवर्ड: <आपका TestPyPI टोकन>


एक बार यह पूरा हो जाने पर, आपको अपने नव-वितरित पैकेज की जांच करने के लिए TestPyPI पर जाने में सक्षम होना चाहिए!


गिटहब सेटअप

लक्ष्य टैग के आधार पर आपके पैकेज के नए संस्करणों को लगातार अपडेट करने के साधन के रूप में GitHub का उपयोग करना है।


सबसे पहले, अपने GitHub वर्कफ़्लो पर Actions टैब पर जाएँ और एक नया वर्कफ़्लो चुनें। हम Publish Python Package वर्कफ़्लो का उपयोग करेंगे जो GitHub Actions द्वारा बनाया गया था। ध्यान दें कि वर्कफ़्लो को रिपॉजिटरी रहस्यों से पढ़ने की आवश्यकता कैसे होती है? सुनिश्चित करें कि आपने अपना PyPI टोकन निर्दिष्ट नाम के तहत संग्रहीत किया है (PyPI API टोकन प्राप्त करना TestPyPI के समान है)।


एक बार वर्कफ़्लो बन जाने के बाद, हम अपने कोड को v1.0.0 टैग करने के लिए आगे बढ़ाएंगे। संस्करण नामकरण सिंटैक्स पर अधिक जानकारी के लिए, यहां Py-Pkgs द्वारा एक बेहतरीन स्पष्टीकरण दिया गया है: दस्तावेज़ीकरण का लिंक


बस सामान्य pull चलाएँ, add और commit कमांड चलाएँ। इसके बाद, निम्नलिखित कमांड चलाकर अपने नवीनतम कमिट के लिए एक टैग बनाएं (टैग पर अधिक जानकारी के लिए: दस्तावेज़ीकरण से लिंक करें )

 git tag <tagname> HEAD


अंत में, अपने नए टैग को रिमोट रिपॉजिटरी पर पुश करें

 git push <remote name> <tag name>


यदि आप अधिक जानना चाहते हैं तो यहां आपके पायथन पैकेज के साथ सीआई/सीडी को एकीकृत करने पर करोल होरोसिन का एक बेहतरीन लेख है। लेकिन अभी के लिए, आराम से बैठें और अपनी नवीनतम उपलब्धि का आनंद लें 🎉। GitHub Actions वर्कफ़्लो के रूप में जादू को खुलते हुए देखने के लिए स्वतंत्र महसूस करें क्योंकि यह आपके पैकेज को PyPI में वितरित करता है।


लपेटें

यह एक लंबा था 😓. इस ट्यूटोरियल के माध्यम से, आपने Typer मॉड्यूल का उपयोग करके अपने सॉफ़्टवेयर को सीएलआई प्रोग्राम में बदलना और अपने पैकेज को PyPI में वितरित करना सीखा। गहराई से जानने के लिए, आपने कमांड और कमांड समूहों को परिभाषित करना, एक इंटरैक्टिव सीएलआई सत्र विकसित करना और कीबोर्ड रुकावट जैसे सामान्य सीएलआई परिदृश्यों पर ध्यान देना सीखा।


आप इस सब से निपटने में एक पूर्ण जादूगर रहे हैं। क्या आप भाग 3 के लिए मेरे साथ नहीं जुड़ेंगे, जहाँ हम वैकल्पिक कार्यात्मकताएँ लागू करते हैं?