pyproject.toml, লকফাইল আর src/ লেআউট — আধুনিক প্রজেক্টের কাঠামো
setup.py থেকে pyproject.toml, uv.lock দিয়ে পুনরুৎপাদনযোগ্য বিল্ড, __init__.py আর src/ লেআউট — আর একটি ফ্ল্যাট স্ক্রিপ্ট-ফোল্ডারকে কমান্ডসহ আসল প্যাকেজে রূপান্তর।
কীভাবে শিখতে চান?
যে সমস্যাটা আমরা সমাধান করছি
টিকিট-বাছাইয়ের কাজটি শুরু হয়েছিল একটি ফোল্ডারে কয়েকটি স্ক্রিপ্ট দিয়ে: clean.py, score.py, run_triage.py। যিনি লিখেছেন, তিনি ফোল্ডারের ভেতরে দাঁড়িয়ে python run_triage.py চালান, আর সব চলে। তারপর তিন মাসে যা যা ঘটল:
- একজন সহকর্মী একটি রিপোর্ট-স্ক্রিপ্ট
scripts/সাবফোল্ডারে রাখলেন, আর সেটিModuleNotFoundError: No module named 'score'দিল — অথচscore.pyঠিক পাশের ফোল্ডারেই। - কেউ API কী রাখার জন্য ফোল্ডারে
secrets.pyনামে একটি ফাইল বানালেন, আর হঠাৎ অন্য একটি স্ক্রিপ্টে পাইথনের নিজস্বsecretsমডিউল «ভেঙে» গেল। - নতুন একজন যোগ দিলেন; পুরনো
setup.pyআরrequirements.txt-এ দুই রকম প্যাকেজের তালিকা, কোনটি ঠিক কেউ জানে না।
কোডে কোনো ভুল নেই; ভুলটা কাঠামোয়। এই অধ্যায়ে আমরা এই ফোল্ডারটিকে একটি আসল পাইথন প্যাকেজে রূপান্তর করব — একটি pyproject.toml, একটি লকফাইল, আর src/ লেআউট — যা কোম্পানির প্রায় প্রতিটি আধুনিক পাইথন রিপোজিটরির চেহারা।
এই অধ্যায় শেষে আপনি পারবেন
setup.pyথেকেpyproject.toml-এ কেন সরে আসা হলো তা বলতে, আরpyproject.toml-এর মূল অংশগুলো পড়তেuv.lockকী আটকে রাখে তা ব্যাখ্যা করতে, আরuv lock,uv sync,uv sync --lockedঠিক জায়গায় ব্যবহার করতেuv init --packageআর--libদিয়েsrc/লেআউটের প্রজেক্ট বানাতে, আর__init__.py-র কাজ বলতে- ফ্ল্যাট ফোল্ডারের দুটি ইমপোর্ট-ফাঁদ চিনতে, আর
src/লেআউট কেন সেগুলো আগেই ধরে তা দেখাতে - একটি ফ্ল্যাট স্ক্রিপ্ট-ফোল্ডারকে কমান্ড-লাইন এন্ট্রি পয়েন্টসহ একটি প্যাকেজে রূপান্তর করতে
আগে যা জানা লাগবে: Git, শাখা আর Pull Request, আর তার আগের অধ্যায়ের uv init, uv add, uv run।
setup.py থেকে pyproject.toml
পুরনো পাইথন প্রজেক্টে আপনি প্রায়ই এরকম একটি ফাইল দেখবেন:
from setuptools import find_packages, setup
setup(
name="ticket-triage",
version="0.1.0",
packages=find_packages(),
install_requires=["httpx"],
entry_points={"console_scripts": ["triage=ticket_triage.cli:main"]},
)আর তার পাশে একটি requirements.txt, যেখানে প্রায়ই অন্য একটি তালিকা। setup.py-র মূল সমস্যা হলো এটি একটি প্রোগ্রাম: প্রজেক্টের নাম বা নির্ভরতা জানতে হলেও ফাইলটি চালাতে হয়, আর যেহেতু এটি পাইথন, যে কেউ এর ভেতরে যেকোনো কিছু লিখতে পারেন — ফাইল পড়া, শর্ত, এমনকি নেটওয়ার্ক কল। টুলগুলো নিশ্চিত হয়ে কিছু বলতে পারত না।
pyproject.toml একই তথ্য ঘোষণা হিসেবে রাখে — একটি সাধারণ TOML ফাইল, যা চালাতে হয় না, শুধু পড়তে হয়। এখন এটাই পাইথনের মানদণ্ড, আর uv, এডিটর, টেস্ট-টুল, লিন্টার — সবাই এটা পড়ে। uv init --package ticket-triage যা বানায়:
[project]
name = "ticket-triage"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
authors = [
{ name = "Nadia Karim", email = "[email protected]" }
]
requires-python = ">=3.12"
dependencies = []
[project.scripts]
ticket-triage = "ticket_triage:main"
[build-system]
requires = ["uv_build>=0.9.1,<0.10.0"]
build-backend = "uv_build"(লেখকের নাম আর ইমেইল uv আপনার Git সেটিং থেকে নেয়।) তিনটি অংশ:
[project]— প্রজেক্টের পরিচয় আর নির্ভরতা।dependencies-এর তালিকাuv addনিজে সামলায়।[project.scripts]— কমান্ড-লাইন কমান্ড।ticket-triage = "ticket_triage:main"মানে:ticket-triageনামে একটি কমান্ড বানাও, যাticket_triageপ্যাকেজেরmainফাংশন ডাকে।[build-system]— প্রজেক্টটিকে ইনস্টলযোগ্য প্যাকেজে রূপ দেওয়ার টুল («বিল্ড ব্যাকএন্ড»)।uvনিজেরuv_buildব্যবহার করে; অন্য প্রজেক্টেhatchlingবাsetuptoolsদেখতে পারেন — কাজ একই।
খেয়াল করুন, প্রজেক্টের নাম ticket-triage (হাইফেন), কিন্তু পাইথন প্যাকেজের নাম ticket_triage (আন্ডারস্কোর)। হাইফেন পাইথনের নামে চলে না — import ticket-triage মানে বিয়োগ — তাই uv নিজেই রূপান্তর করে।
লকফাইল: «কাজ করেছিল» থেকে «সবসময় একই রকম কাজ করে»
pyproject.toml-এ নির্ভরতা লেখা হয় সীমা হিসেবে: "httpx>=0.28.1" মানে ০.২৮.১ বা তার পরের যেকোনো সংস্করণ। সেটা ইচ্ছাকৃত — আপনি বলছেন কী হলে চলবে। কিন্তু আজ আর তিন মাস পরে ইনস্টল করলে দুই রকম সংস্করণ আসতে পারে, আর তার নিচের নির্ভরতাগুলোর তো কথাই নেই।
uv.lock সেই ফাঁক বন্ধ করে। এতে প্রতিটি প্যাকেজের — নিচের স্তরেরগুলোসহ — হুবহু সংস্করণ আর ফাইলের হ্যাশ লেখা থাকে। রিপোজিটরিতে এটি থাকলে প্রতিটি মেশিন, CI আর প্রোডাকশন সার্ভার একই জিনিস ইনস্টল করে। এটাই «পুনরুৎপাদনযোগ্য বিল্ড»।
চারটি কমান্ড, চারটি কাজ:
uv lock—pyproject.tomlপড়ে লকফাইল বানায় বা হালনাগাদ করে। কিছু ইনস্টল করে না।uv sync— লকফাইল অনুযায়ী.venv-কে হুবহু মিলিয়ে দেয়; দরকার হলে আগে লকফাইলও হালনাগাদ করে।uv sync --locked— একই, কিন্তু লকফাইল পুরনো হলে হালনাগাদ না করে থেমে যায়।uv lock --check— কিছু বদলায় না, শুধু বলে লকফাইলpyproject.toml-এর সাথে মেলে কি না।
uv add আর uv remove নিজেই দুটি ফাইল একসাথে হালনাগাদ করে, তাই সাধারণ কাজে এগুলো আলাদা করে ডাকতে হয় না। সমস্যা হয় যখন কেউ pyproject.toml হাতে সম্পাদনা করেন। ধরা যাক কেউ dependencies = []-কে হাতে dependencies = ["httpx>=0.28"] করে দিলেন, কিন্তু uv lock চালালেন না:
uv sync --locked
Resolved 8 packages in 57ms
The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`.এজন্যই CI-তে সবসময় uv sync --locked: যা রিভিউ হয়নি এমন কোনো সংস্করণ সেখানে চুপচাপ ঢুকতে পারে না।
সাধারণuv syncএখানে কোনো ত্রুটি দেয় না। একই অবস্থায়--lockedছাড়া চালালেuvচুপচাপ নতুন করে সমাধান খোঁজে,uv.lockনিজে থেকে বদলে দেয়, আর কাজ চালিয়ে যায়। নিজের মেশিনে সেটা সুবিধাজনক; CI-তে সেটা মানে রিপোজিটরিতে যা আছে তা নয়, অন্য কিছু পরীক্ষা হচ্ছে।
প্যাকেজ, __init__.py আর src/ লেআউট
একটি মডিউল হলো একটি .py ফাইল। একটি প্যাকেজ হলো মডিউলের একটি ফোল্ডার, যার ভেতরে একটি __init__.py আছে। __init__.py দুটি কাজ করে: ফোল্ডারটিকে একটি ইমপোর্টযোগ্য প্যাকেজ হিসেবে চিহ্নিত করে, আর import ticket_triage লিখলে প্রথমে এই ফাইলটিই চলে — তাই প্যাকেজের «সামনের দরজা» হিসেবে যা বাইরে দেখাতে চান, তা এখানে রাখা যায়।
uv init-এর তিনটি রূপ:
uv init— একটি স্ক্রিপ্ট-প্রজেক্ট:main.pyআরpyproject.toml, বিল্ড সিস্টেম নেই। এক-দুই ফাইলের কাজ বা পরীক্ষার জন্য।uv init --package— একটি অ্যাপ্লিকেশন প্যাকেজ:src/লেআউট, বিল্ড সিস্টেম, আর[project.scripts]-এ একটি কমান্ড।uv init --lib— একটি লাইব্রেরি, যা অন্যরা ইমপোর্ট করবে:src/লেআউট আর বিল্ড সিস্টেম, কোনো কমান্ড নেই, আর একটি খালিpy.typedফাইল, যা টাইপ-চেকারদের জানায় যে প্যাকেজটিতে টাইপ তথ্য আছে।
uv init --package ticket-triage-এর কাঠামো:
ticket-triage/
├── .git/
├── .gitignore
├── .python-version
├── README.md
├── pyproject.toml
└── src/
└── ticket_triage/
└── __init__.pyপ্যাকেজটি সরাসরি প্রজেক্টের মূলে না থেকে একটি src/ ফোল্ডারের ভেতরে। এই একটি অতিরিক্ত স্তরের কারণ বুঝতে হলে আগে দেখতে হবে ফ্ল্যাট ফোল্ডারে কী ভুল হয়।
ফ্ল্যাট ফোল্ডারের দুটি ফাঁদ
প্রথম ফাঁদ: পাইথন কোথায় খোঁজে। একটি স্ক্রিপ্ট চালালে পাইথন ইমপোর্টের জন্য প্রথমে খোঁজে ওই স্ক্রিপ্টটি যে ফোল্ডারে আছে, সেখানে — আপনি টার্মিনালে কোথায় দাঁড়িয়ে আছেন সেখানে নয়। ফ্ল্যাট ফোল্ডারে from score import priority কাজ করে কেবল কারণ score.py আর স্ক্রিপ্টটি একই ফোল্ডারে। সহকর্মীর scripts/report.py:
from score import priority
print(priority("Database breach reported"))triage-scripts ফোল্ডারের ভেতরে দাঁড়িয়ে python scripts/report.py চালালে:
Traceback (most recent call last):
File "scripts\report.py", line 1, in <module>
from score import priority
ModuleNotFoundError: No module named 'score'(ট্রেসব্যাকে আপনার মেশিনে পুরো পথ দেখাবে; এখানে ছোট করা হয়েছে।) পাইথন খুঁজেছে scripts/ ফোল্ডারে, আর সেখানে score.py নেই। ইমপোর্ট কাজ করবে কি না, তা নির্ভর করছে ফাইলটি কোথায় রাখা — একটি প্রজেক্টের জন্য এর চেয়ে ভঙ্গুর নিয়ম কমই আছে।
দ্বিতীয় ফাঁদ: নামের সংঘাত। একই নিয়মের কারণে স্ক্রিপ্টের ফোল্ডারের ফাইলগুলো পাইথনের নিজস্ব মডিউলের আগে খোঁজা হয়। ফোল্ডারে কেউ secrets.py বানিয়ে তাতে একটি কী রাখলেন, আর পাশের new_id.py পাইথনের স্ট্যান্ডার্ড লাইব্রেরির secrets ব্যবহার করতে চায়:
import secrets
print(len(secrets.token_hex(8)))Traceback (most recent call last):
File "new_id.py", line 3, in <module>
print(len(secrets.token_hex(8)))
^^^^^^^^^^^^^^^^^
AttributeError: module 'secrets' has no attribute 'token_hex'import secrets কোনো ত্রুটি দেয়নি — সে প্রজেক্টের secrets.py পেয়ে সেটাই এনেছে। ত্রুটি আসে পরে, যখন কোড এমন কিছু চায় যা ওই ফাইলে নেই। এআই প্রজেক্টে এটা প্রায়ই ঘটে openai.py, anthropic.py বা httpx.py নামের পরীক্ষামূলক ফাইল দিয়ে, যা ঠিক যে লাইব্রেরি ব্যবহার করতে চাইছেন সেটাকেই ঢেকে দেয়।
src/ লেআউট কীভাবে এই ভুলগুলো আগেই ধরে
src/ লেআউটে দুটি জিনিস বদলায়।
প্রথমত, সবকিছু একটি অনন্য নামের নিচে। মডিউলগুলো আর clean, score, secrets নয় — তারা ticket_triage.clean, ticket_triage.score। প্রজেক্টের কোনো ফাইল আর স্ট্যান্ডার্ড লাইব্রেরি বা অন্য প্যাকেজের নাম ঢাকতে পারে না, আর ইমপোর্টগুলো পড়েই বোঝা যায় কোনটি নিজের কোড।
দ্বিতীয়ত, প্যাকেজটি ইনস্টল না করে ইমপোর্ট করা যায় না। প্রজেক্টের মূলে দাঁড়িয়ে সাধারণ python দিয়ে চেষ্টা করলে:
python -c "import ticket_triage"
Traceback (most recent call last):
File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'ticket_triage'ব্যর্থতাটিই সুবিধা। প্যাকেজটি প্রজেক্টের মূলে থাকলে এই কমান্ড কাজ করত — পাইথন ফোল্ডারটি সরাসরি খুঁজে পেত — আর আপনি কখনো জানতেন না যে pyproject.toml-এর কনফিগারেশন ঠিক আছে কি না। src/-এর ভেতরে থাকায় প্যাকেজে পৌঁছানোর একটাই পথ: ইনস্টল। আর uv run প্রতিবার সেটা করে — প্রজেক্টটিকে .venv-এ «editable» হিসেবে ইনস্টল করে, অর্থাৎ ইনস্টল করা প্যাকেজটি আপনার src/ ফোল্ডারের দিকেই নির্দেশ করে, তাই কোড বদলালে আবার ইনস্টল করতে হয় না:
uv run python -c "import ticket_triage; print(ticket_triage.priority('OUTAGE'))"
highফলে প্যাকেজিংয়ে কোনো ভুল থাকলে সেটি আপনার মেশিনেই, প্রথম uv run-এ ধরা পড়ে — প্রোডাকশনে ডিপ্লয়ের দিন নয়। যেমন, প্যাকেজের ফোল্ডারের নাম ভুল হলে বা __init__.py না থাকলে:
× Failed to build `ticket-triage @
│ file:///.../ticket-triage`
╰─▶ Expected a Python module at: src\ticket_triage\__init__.py(পথটি ছোট করা হয়েছে।) বিল্ড ব্যাকএন্ড প্রজেক্টের নাম থেকে প্যাকেজের নাম অনুমান করে — ticket-triage থেকে src/ticket_triage/ — আর সেখানে কিছু না পেলে থেমে যায়। ইচ্ছে করে অন্য নাম রাখতে চাইলে pyproject.toml-এ [tool.uv.build-backend]-এর নিচে module-name দিয়ে সেটা বলে দেওয়া যায়।
একটা সম্পূর্ণ উদাহরণ
এবার অধ্যায়ের শুরুর ফ্ল্যাট ফোল্ডারটি রূপান্তর করা যাক। পাশের ফোল্ডারে uv init --package ticket-triage --python 3.12 চালিয়ে, পুরনো ফাইলগুলো প্যাকেজের ভেতরে সরিয়ে, শেষে কাঠামোটি এরকম:
ticket-triage/
├── pyproject.toml
├── uv.lock
├── scripts/
│ └── report.py
└── src/
└── ticket_triage/
├── __init__.py
├── clean.py
├── cli.py
└── score.pypyproject.toml-এ একটিমাত্র বদল — কমান্ডের নাম ছোট করে নতুন cli মডিউলের দিকে নির্দেশ:
[project.scripts]
triage = "ticket_triage.cli:main"src/ticket_triage/clean.py — পুরনো clean.py, এখন টাইপ হিন্ট আর ডকস্ট্রিংসহ:
import re
def clean_text(text: str) -> str:
"""Lowercase the text and collapse every run of whitespace to one space."""
return re.sub(r"\s+", " ", text).strip().lower()src/ticket_triage/score.py — একমাত্র আসল বদল ইমপোর্ট লাইনে: from clean import ... এখন from ticket_triage.clean import ...:
from ticket_triage.clean import clean_text
URGENT_WORDS = ("outage", "down", "breach")
def priority(text: str) -> str:
words = clean_text(text).split()
return "high" if any(word in URGENT_WORDS for word in words) else "normal"src/ticket_triage/__init__.py — প্যাকেজের সামনের দরজা:
from ticket_triage.score import priority
__all__ = ["priority"]scripts/report.py — যে স্ক্রিপ্টটি আগে ভেঙেছিল, এখন প্যাকেজের নাম দিয়ে ইমপোর্ট করে:
from ticket_triage import priority
print(priority("Database breach reported"))আর src/ticket_triage/cli.py — পুরনো run_triage.py-এর জায়গায় কমান্ড-লাইন এন্ট্রি পয়েন্ট:
import sys
from ticket_triage.score import priority
DEMO_TICKETS = ["Login page is slow", "Payments DOWN for all users"]
def main() -> None:
tickets = sys.argv[1:] or DEMO_TICKETS
for ticket in tickets:
print(f"{priority(ticket):<6} {ticket}")
if __name__ == "__main__":
main()প্রজেক্টের মূলে দাঁড়িয়ে চালানো হলো:
uv run triage
uv run triage "Service down since morning" "Typo on the pricing page"
uv run scripts/report.py
uv lock --checknormal Login page is slow
high Payments DOWN for all users
high Service down since morning
normal Typo on the pricing page
high
Resolved 1 package in 2msপ্রথমবার uv run triage চালালে এর আগে কয়েকটি লাইন আসে — .venv তৈরি, আর Building ticket-triage ও Built ticket-triage — কারণ uv প্রজেক্টটিকে প্রথমবার ইনস্টল করছে; পরের বার থেকে আর আসে না। শেষ লাইনটি uv lock --check-এর, আর তার এক্সিট কোড 0: লকফাইল pyproject.toml-এর সাথে মেলে।
এই রূপান্তরের সিদ্ধান্তগুলো:
পরম ইমপোর্ট (from ticket_triage.clean import ...)। প্যাকেজের ভেতরে আপেক্ষিক ইমপোর্টও চলে — from .clean import clean_text — কিন্তু পরম ইমপোর্ট যেকোনো ফাইল থেকে একই রকম পড়া যায় আর সার্চ করা সহজ। যেটাই বেছে নিন, একটি প্রজেক্টে একটাই রাখুন।
__init__.py-তে শুধু priority। বাইরের কোড (scripts/report.py, ভবিষ্যতের টেস্ট, অন্য সার্ভিস) জানে শুধু from ticket_triage import priority। ভেতরে score.py আর clean.py কীভাবে ভাগ করা, সেটা পরে বদলালেও বাইরের কোড ভাঙে না। __all__ সেই প্রকাশ্য তালিকাটি স্পষ্ট করে লিখে রাখে।
cli.py আলাদা। যুক্তি (score.py) আর ইনপুট-আউটপুট (cli.py) আলাদা থাকায় priority যেকোনো জায়গা থেকে ডাকা যায় — টেস্ট থেকে, একটি API থেকে — sys.argv বা print ছাড়াই। sys.argv[1:] খালি হলে ডেমো টিকিট চলে, তাই আর্গুমেন্ট ছাড়া চালালেও কিছু দেখা যায়।
f"{priority(ticket):<6}"। :<6 লেখাটিকে ছয় ঘরের মধ্যে বাঁদিকে বসায়, তাই high আর normal দুটোর পরেই টিকিটগুলো একই কলামে শুরু হয়।
একটি সীমাবদ্ধতা জেনে রাখা ভালো: clean_text শুধু ফাঁকা জায়গা দিয়ে শব্দ ভাগ করে, তাই "The site is down."-এ শব্দটি down. — দাঁড়িসহ — আর সেটি URGENT_WORDS-এ মেলে না। লেখা থেকে শব্দ বের করার আরও ভালো উপায় কোর্সে একটু পরেই আসছে; এই অধ্যায়ের বিষয় কাঠামো, আর কাঠামো ঠিক থাকায় সেই উন্নতিটি হবে শুধু clean.py-তে, অন্য কোথাও নয়।
কিছু ভাঙা অবস্থা ও তার সমাধান
ModuleNotFoundError: No module named 'clean' ফাইলগুলো প্যাকেজে সরানো হয়েছে, কিন্তু কোনো ফাইলে পুরনো ইমপোর্ট from clean import clean_text রয়ে গেছে। প্যাকেজের ভেতরে clean নামে কোনো শীর্ষ-স্তরের মডিউল নেই; লিখুন from ticket_triage.clean import clean_text। রূপান্তরের পর পুরো প্রজেক্টে from clean, from score খুঁজে দেখুন।
ModuleNotFoundError: No module named 'ticket_triage' uv run ছাড়া সাধারণ python দিয়ে চালানো হয়েছে, তাই প্যাকেজটি ইনস্টল করা পরিবেশ ব্যবহার হয়নি। uv run ... দিয়ে চালান। এটা src/ লেআউটের ইচ্ছাকৃত আচরণ, ত্রুটি নয়।
Expected a Python module at: src\ticket_triage\__init__.py বিল্ড ব্যাকএন্ড প্রজেক্টের নাম অনুযায়ী যে ফোল্ডার আশা করছে, সেখানে প্যাকেজ নেই — ফোল্ডারের নাম ভুল (src/triage/, src/ticket-triage/), বা __init__.py নেই। ফোল্ডারের নাম ঠিক করুন, __init__.py যোগ করুন, অথবা module-name দিয়ে আসল নামটি বলে দিন।
The lockfile at uv.lock needs to be updated, but --locked was provided. pyproject.toml হাতে বদলানো হয়েছে কিন্তু uv lock চালানো হয়নি, বা লকফাইলটি কমিট করতে ভুলে গেছেন। uv lock চালিয়ে দুটি ফাইলই একসাথে কমিট করুন। পরের বার নির্ভরতা বদলাতে uv add আর uv remove ব্যবহার করুন — তারা দুটি ফাইল একসাথে হালনাগাদ রাখে।
AttributeError: module 'secrets' has no attribute 'token_hex' প্রজেক্টে স্ট্যান্ডার্ড লাইব্রেরির কোনো মডিউলের নামে একটি ফাইল আছে (secrets.py, random.py, logging.py), যা আসলটিকে ঢেকে দিয়েছে। ফাইলটির নাম বদলান — আর গোপন তথ্য কোনো .py ফাইলে নয়, .env-এ রাখুন।
error: Failed to spawn: triag তারপর Caused by: program not found [project.scripts]-এ যে নাম লেখা, uv run-এ তার চেয়ে আলাদা নাম দেওয়া হয়েছে — এখানে বানান ভুল। pyproject.toml-এ নামটি দেখে নিন; নাম বদলানোর পর uv run নিজেই প্রজেক্টটি আবার ইনস্টল করে নতুন কমান্ড বানায়।
ধাপ ৪ / ৬ — অনুমান
যাচাই করুন
অধ্যায়ের clean_text একটি অগোছালো টিকিটের উপর চালানো হলো। কী ছাপা হবে?
import re
def clean_text(text: str) -> str:
return re.sub(r"\s+", " ", text).strip().lower()
print(repr(clean_text(" Payments DOWN\tsince 9 ")))- A`' payments down since 9 '`
- B`'payments down since 9'`
- C`'payments down\tsince 9'`
- D`'Payments DOWN since 9'`
ফ্ল্যাট ফোল্ডার থেকে src/ লেআউটে সরানোর পর uv run triage চালালে ModuleNotFoundError: No module named 'clean' আসে। clean.py ঠিক পাশেই, src/ticket_triage/clean.py-তে আছে। ভুলটি কী?
# src/ticket_triage/score.py
from clean import clean_text
URGENT_WORDS = ("outage", "down", "breach")
def priority(text: str) -> str:
words = clean_text(text).split()
return "high" if any(word in URGENT_WORDS for word in words) else "normal"- A`clean.py`-কে প্রজেক্টের মূলে ফিরিয়ে নিতে হবে, কারণ পাইথন সেখানে খোঁজে
- Bইমপোর্টটি এখনো ফ্ল্যাট ফোল্ডারের ধাঁচে; প্যাকেজের ভেতরে লিখতে হবে `from ticket_triage.clean import clean_text`
- C`src/ticket_triage/`-এ `__init__.py` থাকলে পাশের ফাইল ইমপোর্ট করা যায় না; সেটি মুছতে হবে
- D`uv run triage` কেবল `cli.py` দেখে; `score.py`-এর ইমপোর্ট কখনো চলে না
একটি PR-এ কেউ pyproject.toml হাতে সম্পাদনা করে একটি প্যাকেজ যোগ করেছেন। CI-এর uv sync --locked ব্যর্থ: The lockfile at uv.lock needs to be updated। সঠিক সমাধান কোনটি?
- ACI থেকে `--locked` সরিয়ে দেওয়া, যাতে CI নিজেই লকফাইল হালনাগাদ করে নেয়
- B`uv.lock`-কে `.gitignore`-এ যোগ করা, যাতে প্রত্যেকে নিজের লকফাইল বানান
- Cনিজের মেশিনে `uv lock` চালিয়ে (বা শুরু থেকেই `uv add` দিয়ে) হালনাগাদ `uv.lock` একই PR-এ কমিট করা
- D`uv.lock` মুছে দেওয়া, কারণ লকফাইল ছাড়া এই ত্রুটি আর আসবে না
প্লে প্রো-এর অংশ
+30 XP অপেক্ষায়
প্রো দিয়ে এই অধ্যায়টি খেলুন
পড়া ফ্রি, পুরো অধ্যায়টিই খোলা। প্রতিটি ফ্রি কোর্সের প্রথম 5টি অধ্যায় ফ্রিতে খেলা যায়। অধ্যায় 6 থেকে প্রশ্নের উত্তর দেওয়া, XP পাওয়া আর স্ট্রিক বাড়ানো প্রো-এর অংশ।
প্রো দিয়ে প্লে চালু করুননিজে করুন
অধ্যায়ের শুরুর ফ্ল্যাট ফোল্ডারটি নিজে বানিয়ে রূপান্তর করুন। প্রথমে একটি triage-scripts/ ফোল্ডারে তিনটি ফাইল রাখুন: clean.py (উপরের clean_text, টাইপ হিন্ট ছাড়া), score.py (from clean import clean_text দিয়ে শুরু), আর run_triage.py, যা দুটি ডেমো টিকিটের অগ্রাধিকার ছাপায়। তারপর:
- পাশে
uv init --package ticket-triage --python 3.12দিয়ে নতুন প্রজেক্ট বানিয়ে ফাইলগুলোsrc/ticket_triage/-এ সরান, আর প্রতিটি ইমপোর্টticket_triage.দিয়ে লিখুন run_triage.py-কে এই অধ্যায়ের মতোcli.py-তে একটিmain()ফাংশন বানিয়ে (একই দুটি ডেমো টিকিটসহ)[project.scripts]-এtriage = "ticket_triage.cli:main"যোগ করুন__init__.pyথেকে শুধুpriorityপ্রকাশ করুন, আরscripts/report.pyথেকেfrom ticket_triage import priorityদিয়ে সেটি ব্যবহার করুনuv lock --checkচালিয়ে নিশ্চিত হন লকফাইল মেলে, আরpyproject.toml,uv.lock,src/,scripts/একটি কমিটে দিন —.venvছাড়া
প্রজেক্টের মূলে দাঁড়িয়ে uv run triage আর তারপর uv run scripts/report.py চালালে ঠিক এটা আসা উচিত:
normal Login page is slow
high Payments DOWN for all users
highতারপর ইচ্ছে করে দুবার ভাঙুন। একবার score.py-তে ইমপোর্টটি আবার from clean import clean_text করে দেখুন কোন ত্রুটি আসে আর কোন লাইনে। আরেকবার src/ticket_triage/-এর ভেতরে একটি secrets.py বানিয়ে cli.py-তে import secrets আর print(len(secrets.token_hex(8))) যোগ করুন, আর uv run triage চালিয়ে দেখুন — ফ্ল্যাট ফোল্ডারের ফাঁদটি এখানে আর কাজ করে কি না, আর কেন।
ধাপ ৬ / ৬
কঠিন করা — অধ্যায়ের কুইজ
সহজ থেকে কঠিন — দশটি প্রশ্ন, শেষেরগুলো ইচ্ছে করেই কঠিন।
প্রো দিয়ে খেলুন