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) , আমরা ট্রেলো SDK-এর সাথে যোগাযোগ করার জন্য সফলভাবে ব্যবসায়িক যুক্তি তৈরি করেছি।


এখানে আমাদের CLI প্রোগ্রামের আর্কিটেকচারের একটি দ্রুত সংক্ষিপ্ত বিবরণ দেওয়া হল:

প্রয়োজনীয়তার উপর ভিত্তি করে CLI কাঠামোর বিস্তারিত টেবিল ভিউ


এই টিউটোরিয়ালে, আমরা দেখব কীভাবে আমাদের প্রকল্পটিকে একটি CLI প্রোগ্রামে রূপান্তর করা যায়, কার্যকরী এবং অ-কার্যকর প্রয়োজনীয়তার উপর ফোকাস করে।


অন্যদিকে, আমরা PyPI- তে প্যাকেজ হিসাবে আমাদের প্রোগ্রামটি কীভাবে বিতরণ করতে হয় তাও শিখব।


চল শুরু করি


ফোল্ডার স্ট্রাকচার

পূর্বে, আমরা আমাদের trelloservice মডিউল হোস্ট করার জন্য একটি কঙ্কাল সেট আপ করতে পেরেছিলাম। এই সময়ে, আমরা বিভিন্ন কার্যকারিতার জন্য মডিউল সহ একটি cli ফোল্ডার বাস্তবায়ন করতে চাই, যথা:


  • কনফিগারেশন
  • অ্যাক্সেস
  • তালিকা


ধারণাটি হল যে, প্রতিটি কমান্ড গ্রুপের জন্য, এর কমান্ডগুলি নিজস্ব মডিউলে সংরক্ষণ করা হবে। list কমান্ডের জন্য, আমরা এটিকে প্রধান CLI ফাইলে সংরক্ষণ করব কারণ এটি কোনো কমান্ড গ্রুপের অন্তর্গত নয়।


অন্যদিকে, আসুন আমাদের ফোল্ডার কাঠামো পরিষ্কার করার দিকে তাকাই। আরও সুনির্দিষ্টভাবে, ডিরেক্টরিগুলি যাতে বিশৃঙ্খল না হয় তা নিশ্চিত করে আমাদের সফ্টওয়্যারের মাপযোগ্যতার জন্য অ্যাকাউন্টিং শুরু করা উচিত।


এখানে আমাদের ফোল্ডার গঠন একটি পরামর্শ আছে:

 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 মডিউল, 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 মডিউলে সংরক্ষিত আছে রানটাইম চলাকালীন কার্যকর করা হবে।


এখন যেহেতু আমাদের সফ্টওয়্যারের CLI অংশটি সেট আপ করা হয়েছে, আসুন আমাদের CLI প্রোগ্রামটিকে আরও ভালভাবে পরিবেশন করার জন্য আমাদের 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 অবজেক্ট ইনস্ট্যান্টিয়েট করা যাক। সম্পূর্ণ কোডটি কেমন দেখাচ্ছে তা দেখতে নির্দ্বিধায় এই প্যাচটি দেখুন: trello-cli-kit

 # trellocli/trelloservice.py trellojob = TrelloService()


অবশেষে, আমরা প্রোগ্রাম জুড়ে শেয়ার করার জন্য কিছু কাস্টম ব্যতিক্রম শুরু করতে চাই। এটি আমাদের ইনিশিয়েলাইজারে সংজ্ঞায়িত ERRORS থেকে ভিন্ন কারণ এই ব্যতিক্রমগুলি BaseException থেকে উপশ্রেণী এবং সাধারণ ব্যবহারকারী-সংজ্ঞায়িত ব্যতিক্রম হিসাবে কাজ করে, যেখানে ERRORS গুলি 0 থেকে শুরু হওয়া ধ্রুবক মান হিসাবে আরও কাজ করে।


আসুন আমাদের ব্যতিক্রমগুলিকে ন্যূনতম পর্যন্ত রাখি এবং কিছু সাধারণ ব্যবহারের ক্ষেত্রে, বিশেষত উল্লেখযোগ্যভাবে:


  • পড়ার ত্রুটি: Trello থেকে পড়ার সময় একটি ত্রুটি দেখা দিলে উত্থাপিত হয়
  • লিখতে ত্রুটি: Trello-এ লেখার সময় একটি ত্রুটি দেখা দিলে উত্থাপিত হয়
  • অনুমোদন ত্রুটি: Trello-এর জন্য অনুমোদন না দেওয়া হলে উত্থাপিত হয়
  • অবৈধ ব্যবহারকারী ইনপুট ত্রুটি: ব্যবহারকারীর CLI ইনপুট স্বীকৃত না হলে উত্থাপিত হয়৷


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


ইউনিট পরীক্ষা

প্রথম অংশে উল্লিখিত হিসাবে, আমরা এই টিউটোরিয়ালে ইউনিট টেস্টগুলিকে ব্যাপকভাবে কভার করব না, তাই আসুন শুধুমাত্র প্রয়োজনীয় উপাদানগুলির সাথে কাজ করি:


  • অ্যাক্সেস কনফিগার করতে পরীক্ষা করুন
  • ট্রেলো বোর্ড কনফিগার করতে পরীক্ষা করুন
  • একটি নতুন ট্রেলো কার্ড তৈরি করতে পরীক্ষা করুন
  • ট্রেলো বোর্ডের বিবরণ প্রদর্শন করতে পরীক্ষা করুন
  • ট্রেলো বোর্ডের বিশদ বিবরণ প্রদর্শন করতে পরীক্ষা করুন (বিশদ দৃশ্য)


ধারণাটি প্রত্যাশিত ফলাফলের জন্য পরীক্ষা করার জন্য একটি shell মতো একটি কমান্ড লাইন দোভাষীকে উপহাস করা। Typer মডিউল সম্পর্কে যা দুর্দান্ত তা হল এটি তার নিজস্ব runner অবজেক্টের সাথে আসে। পরীক্ষা চালানোর জন্য, আমরা এটিকে pytest মডিউলের সাথে যুক্ত করব। আরও তথ্যের জন্য, Typer দ্বারা অফিসিয়াল ডক্স দেখুন।


আসুন একসাথে প্রথম পরীক্ষার মাধ্যমে কাজ করি, অর্থাৎ অ্যাক্সেস কনফিগার করা। বুঝুন যে আমরা পরীক্ষা করছি যদি ফাংশনটি সঠিকভাবে কার্যকর হয়। এটি করার জন্য, আমরা সিস্টেমের প্রতিক্রিয়া পরীক্ষা করব এবং এক্সিট কোডটি success ওরফে 0 কিনা। এখানে রেডহ্যাটের একটি দুর্দান্ত নিবন্ধ রয়েছে যে এক্সিট কোডগুলি কী এবং কীভাবে সিস্টেম প্রক্রিয়াগুলিকে যোগাযোগ করতে তাদের ব্যবহার করে

 # 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 মডিউল

বুঝুন যে এটি আমাদের প্রধান cli মডিউল হবে - সমস্ত কমান্ড গ্রুপের জন্য (কনফিগ, তৈরি করুন), তাদের ব্যবসায়িক যুক্তি ভাল পঠনযোগ্যতার জন্য নিজস্ব পৃথক ফাইলে সংরক্ষণ করা হবে।


এই মডিউলে, আমরা আমাদের list কমান্ড সংরক্ষণ করব। কমান্ডের গভীরে ডুব দিয়ে, আমরা জানি যে আমরা নিম্নলিখিত বিকল্পগুলি বাস্তবায়ন করতে চাই:


  • board_name: config board আগে সেট করা না থাকলে প্রয়োজনীয়
  • বিস্তারিত: একটি বিশদ দৃশ্যে প্রদর্শন করুন


board_name প্রয়োজনীয় বিকল্পটি দিয়ে শুরু করে, এটি অর্জন করার কয়েকটি উপায় রয়েছে, তাদের মধ্যে একটি হল কলব্যাক ফাংশন ব্যবহার করে (আরো তথ্যের জন্য, এখানে অফিসিয়াল ডক্স রয়েছে) বা কেবল একটি ডিফল্ট পরিবেশ পরিবর্তনশীল ব্যবহার করে। যাইহোক, আমাদের ব্যবহারের ক্ষেত্রে, শর্ত পূরণ না হলে আমাদের InvalidUserInputError কাস্টম ব্যতিক্রম উত্থাপন করে এটিকে সোজা রাখি।


কমান্ড তৈরি করতে, আসুন বিকল্পগুলি সংজ্ঞায়িত করে শুরু করি। Typer-এ, তাদের অফিসিয়াল ডক্সে উল্লিখিত হিসাবে, একটি বিকল্প সংজ্ঞায়িত করার মূল উপাদানগুলি হবে:


  • ডেটা টাইপ
  • সহায়ক পাঠ্য
  • ডিফল্ট মান


উদাহরণস্বরূপ, নিম্নলিখিত শর্তগুলির সাথে detailed বিকল্প তৈরি করতে:


  • ডেটা টাইপ: bool
  • হেল্পার টেক্সট: "বিস্তারিত ভিউ সক্ষম করুন"
  • ডিফল্ট মান: কোনোটিই নয়


আমাদের কোড এই মত দেখাবে:

 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 বিকল্প নির্বাচন করা হয়েছে কিনা তা পরীক্ষা করুন)


কয়েকটি বিষয় খেয়াল রাখতে হবে…


  • আমরা ব্যতিক্রমগুলি বাড়াতে চাই যখন trellojob SUCCESS ছাড়া অন্য একটি স্ট্যাটাস কোড তৈরি করে। try-catch ব্লক ব্যবহার করে, আমরা আমাদের প্রোগ্রামকে মারাত্মক ক্র্যাশ থেকে আটকাতে পারি।
  • ব্যবহারের জন্য উপযুক্ত বোর্ড কনফিগার করার সময়, আমরা পুনরুদ্ধার করা board_id এর উপর ভিত্তি করে ব্যবহারের জন্য ট্রেলো বোর্ড সেট আপ করার চেষ্টা করব। এইভাবে, আমরা নিম্নলিখিত ব্যবহারের ক্ষেত্রে কভার করতে চাই
    • ট্রেলোজব-এ get_all_boards ফাংশন ব্যবহার করে ম্যাচের জন্য পরীক্ষা করে board_name স্পষ্টভাবে প্রদান করা হলে board_id পুনরুদ্ধার করা
    • board_name বিকল্পটি ব্যবহার না করা হলে পরিবেশ পরিবর্তনশীল হিসাবে সংরক্ষিত board_id পুনরুদ্ধার করা
  • আমরা যে ডেটা প্রদর্শন করব তা rich প্যাকেজ থেকে Table কার্যকারিতা ব্যবহার করে ফর্ম্যাট করা হবে। rich সম্পর্কে আরও তথ্যের জন্য, অনুগ্রহ করে তাদের অফিসিয়াল ডক্স দেখুন
    • বিস্তারিত: Trello তালিকার সংখ্যা, কার্ডের সংখ্যা এবং সংজ্ঞায়িত লেবেলের একটি সারসংক্ষেপ প্রদর্শন করুন। প্রতিটি ট্রেলো তালিকার জন্য, সমস্ত কার্ড এবং তাদের সংশ্লিষ্ট নাম, বিবরণ এবং সংশ্লিষ্ট লেবেলগুলি প্রদর্শন করুন
    • অ-বিস্তারিত: ট্রেলো তালিকার সংখ্যা, কার্ডের সংখ্যা এবং সংজ্ঞায়িত লেবেলগুলির একটি সারাংশ প্রদর্শন করুন


সবকিছু একসাথে নির্বাণ, আমরা নিম্নলিখিত হিসাবে কিছু পেতে. দাবিত্যাগ: TrelloService থেকে কিছু অনুপস্থিত ফাংশন থাকতে পারে যা আমরা এখনও বাস্তবায়ন করতে পারিনি। অনুগ্রহ করে এই প্যাচটি পড়ুন যদি সেগুলি বাস্তবায়নের জন্য আপনার সাহায্যের প্রয়োজন হয়: trello-cli-kit

 # trellocli/cli/cli.py # module imports from trellocli.trelloservice import trellojob from trellocli.cli import cli_config, cli_create from trellocli.misc.custom_exceptions import * from trellocli import SUCCESS # dependencies imports from typer import Typer, Option from rich import print from rich.console import Console from rich.table import Table from dotenv import load_dotenv # misc imports from typing_extensions import Annotated import os # singleton instances app = Typer() console = Console() # init command groups app.add_typer(cli_config.app, name="config", help="COMMAND GROUP to initialize configurations") app.add_typer(cli_create.app, name="create", help="COMMAND GROUP to create new Trello elements") @app.command() def list( detailed: Annotated[ bool, Option(help="Enable detailed view") ] = None, board_name: Annotated[str, Option()] = "" ) -> None: """COMMAND to list board details in a simplified (default)/detailed view OPTIONS detailed (bool): request for detailed view board_name (str): board to use """ try: # check authorization res_is_authorized = trellojob.is_authorized() if not res_is_authorized: print("[bold red]Error![/] Authorization hasn't been granted. Try running `trellocli config access`") raise AuthorizationError # if board_name OPTION was given, attempt to retrieve board id using the name # else attempt to retrieve board id stored as an env var board_id = None if not board_name: load_dotenv() if not os.getenv("TRELLO_BOARD_ID"): print("[bold red]Error![/] A trello board hasn't been configured to use. Try running `trellocli config board`") raise InvalidUserInputError board_id = os.getenv("TRELLO_BOARD_ID") else: res_get_all_boards = trellojob.get_all_boards() if res_get_all_boards.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving boards from trello") raise TrelloReadError boards_list = {board.name: board.id for board in res_get_all_boards.res} # retrieve all board id(s) and find matching board name if board_name not in boards_list: print("[bold red]Error![/] An invalid trello board name was provided. Try running `trellocli config board`") raise InvalidUserInputError board_id = boards_list[board_name] # configure board to use res_get_board = trellojob.get_board(board_id=board_id) if res_get_board.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when configuring the trello board to use") raise TrelloReadError board = res_get_board.res # retrieve data (labels, trellolists) from board res_get_all_labels = trellojob.get_all_labels(board=board) if res_get_all_labels.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving data from board") raise TrelloReadError labels_list = res_get_all_labels.res res_get_all_lists = trellojob.get_all_lists(board=board) if res_get_all_lists.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving data from board") raise TrelloReadError trellolists_list = res_get_all_lists.res # store data on cards for each trellolist trellolists_dict = {trellolist: [] for trellolist in trellolists_list} for trellolist in trellolists_list: res_get_all_cards = trellojob.get_all_cards(trellolist=trellolist) if res_get_all_cards.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving cards from trellolist") raise TrelloReadError cards_list = res_get_all_cards.res trellolists_dict[trellolist] = cards_list # display data (lists count, cards count, labels) # if is_detailed OPTION is selected, display data (name, description, labels) for each card in each trellolist print() table = Table(title="Board: "+board.name, title_justify="left", show_header=False) table.add_row("[bold]Lists count[/]", str(len(trellolists_list))) table.add_row("[bold]Cards count[/]", str(sum([len(cards_list) for cards_list in trellolists_dict.values()]))) table.add_row("[bold]Labels[/]", ", ".join([label.name for label in labels_list if label.name])) console.print(table) if detailed: for trellolist, cards_list in trellolists_dict.items(): table = Table("Name", "Desc", "Labels", title="List: "+trellolist.name, title_justify="left") for card in cards_list: table.add_row(card.name, card.description, ", ".join([label.name for label in card.labels if label.name])) console.print(table) print() except (AuthorizationError, InvalidUserInputError, TrelloReadError): print("Program exited...")


আমাদের সফ্টওয়্যারটি কার্যকর দেখতে, টার্মিনালে কেবল python -m trellocli --help চালান। ডিফল্টরূপে, Typer মডিউল --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 কমান্ড গ্রুপ আমদানি করতে পারেন? সাহায্যের জন্য এই প্যাচটি নির্দ্বিধায় উল্লেখ করুন: trello-cli-kit


সাবকমান্ড

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 কমান্ডের জন্য, আমরা ব্যবহারকারীর ইন্টারঅ্যাকশনের জন্য একটি টার্মিনাল GUI প্রদর্শন করার জন্য সাধারণ টার্মিনাল মেনু সহ একটি ভাল ব্যবহারকারীর অভিজ্ঞতা প্রদানের জন্য বিভিন্ন মডিউল ব্যবহার করব। প্রধান ধারণা নিম্নরূপ:


  • অনুমোদনের জন্য চেক করুন
  • ব্যবহারকারীর অ্যাকাউন্ট থেকে সমস্ত ট্রেলো বোর্ড পুনরুদ্ধার করুন
  • ট্রেলো বোর্ডের একটি একক-নির্বাচন টার্মিনাল মেনু প্রদর্শন করুন
  • একটি পরিবেশ পরিবর্তনশীল হিসাবে নির্বাচিত Trello বোর্ড আইডি সেট করুন


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


অবশেষে, আমরা আমাদের সফ্টওয়্যারের মূল কার্যকরী প্রয়োজনীয়তার দিকে এগিয়ে যাচ্ছি - Trello বোর্ডের একটি তালিকায় একটি নতুন কার্ড যোগ করুন। বোর্ড থেকে ডেটা পুনরুদ্ধার না করা পর্যন্ত আমরা আমাদের list কমান্ড থেকে একই পদক্ষেপগুলি ব্যবহার করব।


তা ছাড়াও, আমরা নতুন কার্ডটি সঠিকভাবে কনফিগার করার জন্য ব্যবহারকারীর ইনপুটের জন্য ইন্টারেক্টিভভাবে অনুরোধ করব:


  • ট্রেলো তালিকায় যোগ করা হবে: একক-নির্বাচন
  • কার্ডের নাম: পাঠ্য
  • [ঐচ্ছিক] কার্ডের বিবরণ: পাঠ্য
  • [ঐচ্ছিক] লেবেল: বহু-নির্বাচন
  • নিশ্চিতকরণ: y/N


একটি তালিকা থেকে ব্যবহারকারীকে নির্বাচন করতে হবে এমন সমস্ত প্রম্পটের জন্য, আমরা আগের মতো 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 এ আপলোড করুন
  • গিটহাব অ্যাকশন কনফিগার করুন
  • ট্যাগ 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/*


আপনাকে আপনার ব্যবহারকারীর নাম এবং পাসওয়ার্ড টাইপ করতে বলা হবে। দুটি ফ্যাক্টর প্রমাণীকরণ সক্ষম থাকার কারণে, আপনাকে একটি API টোকেন ব্যবহার করতে হবে (কীভাবে একটি TestPyPI API টোকেন অর্জন করবেন সে সম্পর্কে আরও তথ্যের জন্য: ডকুমেন্টেশনের লিঙ্ক )। সহজভাবে নিম্নলিখিত মান রাখুন:


  • ব্যবহারকারীর নাম: টোকেন
  • পাসওয়ার্ড: <আপনার TestPyPI টোকেন>


একবার এটি সম্পূর্ণ হয়ে গেলে, আপনার সদ্য বিতরণ করা প্যাকেজটি পরীক্ষা করার জন্য আপনি TestPyPI-তে যেতে সক্ষম হবেন!


গিটহাব সেটআপ

লক্ষ্য হল ট্যাগগুলির উপর ভিত্তি করে আপনার প্যাকেজের নতুন সংস্করণগুলি ক্রমাগত আপডেট করার উপায় হিসাবে গিটহাবকে ব্যবহার করা।


প্রথমে, আপনার GitHub ওয়ার্কফ্লোতে Actions ট্যাবে যান এবং একটি নতুন ওয়ার্কফ্লো নির্বাচন করুন। আমরা Publish Python Package ওয়ার্কফ্লো ব্যবহার করব যা গিটহাব অ্যাকশন দ্বারা তৈরি করা হয়েছিল। লক্ষ্য করুন কিভাবে ওয়ার্কফ্লো রিপোজিটরি সিক্রেট থেকে পড়ার প্রয়োজন হয়? নিশ্চিত করুন যে আপনি নির্দিষ্ট নামের অধীনে আপনার PyPI টোকেন সংরক্ষণ করেছেন (একটি PyPI API টোকেন অর্জন করা TestPyPI এর মতই)।


ওয়ার্কফ্লো তৈরি হয়ে গেলে, আমরা v1.0.0 ট্যাগ করার জন্য আমাদের কোডটি পুশ করব। সংস্করণ নামকরণ সিনট্যাক্স সম্পর্কে আরও তথ্যের জন্য, এখানে Py-Pkgs দ্বারা একটি দুর্দান্ত ব্যাখ্যা রয়েছে: ডকুমেন্টেশনের লিঙ্ক


সহজভাবে সাধারণ pull , add এবং commit কমান্ড চালান। এরপরে, নিম্নলিখিত কমান্ডটি চালিয়ে আপনার সর্বশেষ প্রতিশ্রুতির জন্য একটি ট্যাগ তৈরি করুন (ট্যাগ সম্পর্কে আরও তথ্যের জন্য: ডকুমেন্টেশনের লিঙ্ক )

 git tag <tagname> HEAD


অবশেষে, রিমোট রিপোজিটরিতে আপনার নতুন ট্যাগটি পুশ করুন

 git push <remote name> <tag name>


আপনি যদি আরও জানতে চান তাহলে আপনার পাইথন প্যাকেজের সাথে CI/CD একীভূত করার বিষয়ে Karol Horosin- এর একটি দুর্দান্ত নিবন্ধ এখানে রয়েছে। কিন্তু আপাতত, বসে থাকুন এবং আপনার সর্বশেষ কৃতিত্ব উপভোগ করুন 🎉। একটি GitHub অ্যাকশন ওয়ার্কফ্লো হিসাবে যাদুটি উন্মোচিত হতে নির্দ্বিধায় দেখুন কারণ এটি আপনার প্যাকেজটি PyPI-তে বিতরণ করে।


শেষ করি

এটি একটি দীর্ঘ 😓 ছিল. এই টিউটোরিয়ালটির মাধ্যমে, আপনি Typer মডিউল ব্যবহার করে আপনার সফ্টওয়্যারটিকে একটি CLI প্রোগ্রামে রূপান্তর করতে এবং আপনার প্যাকেজটি PyPI-তে বিতরণ করতে শিখেছেন। আরও গভীরে যেতে, আপনি কমান্ড এবং কমান্ড গ্রুপগুলিকে সংজ্ঞায়িত করতে শিখেছেন, একটি ইন্টারেক্টিভ CLI সেশন তৈরি করতে এবং কীবোর্ড বাধার মতো সাধারণ CLI পরিস্থিতিগুলির সাথে ড্যাবল করতে শিখেছেন৷


আপনি এটি সব মাধ্যমে কঠিন আউট জন্য একটি পরম জাদুকর হয়েছে. আপনি কি পার্ট 3 এর জন্য আমার সাথে যোগ দেবেন না, যেখানে আমরা ঐচ্ছিক কার্যকারিতাগুলি বাস্তবায়ন করি?