Asosiy qismga o'tish

Buyruq qatori va paketlash

15-bob 24 kod bloki 3935 so'z

Dastur — bu interfeys

Kutubxonangiz qanchalik yaxshi bo'lmasin, foydalanuvchi uni buyruq qatori orqali ko'radi. Yaxshi CLI ning uch belgisi bor: tushunarli yordam, mantiqiy qaytish kodlari, va bashorat qilinadigan chiqish.

9-bobda argparse ni qisqacha ishlatgandik. Endi uni to'liq qilamiz.

argparse: to'liq interfeys

shu faylni yaratingsrc/hisobot/cli.py

python124 qator
"""Buyruq qatori interfeysi."""
import argparse
import logging
import sys
from pathlib import Path

from . import __version__
from .chiqarish import csv_yoz, json_yoz, xulosa
from .hisob import daraja_taqsimoti, eng_yaxshi, guruh_ortachasi
from .ko_p import hammasi
from .oqish import oqi, tozala
from .sozlama import Sozlama
from .xatolar import HisobotXato, SozlamaXato

log = logging.getLogger(__name__)


def jurnalni_sozla(batafsil: int, jim: bool) -> None:
    if jim:
        daraja = logging.ERROR
    else:
        daraja = {0: logging.WARNING, 1: logging.INFO}.get(batafsil, logging.DEBUG)
    logging.basicConfig(level=daraja, stream=sys.stderr,
                        format="%(levelname)-8s %(name)s: %(message)s")


def tahlilchi() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(
        prog="hisobot",
        description="CSV fayldan talabalar hisobotini tayyorlaydi.",
        epilog="Misol:  hisobot talabalar.csv --format json --chiqish natija.json",
    )
    p.add_argument("fayllar", nargs="+", type=Path, metavar="FAYL",
                   help="bir yoki bir nechta CSV fayl")
    p.add_argument("--format", choices=["matn", "json", "csv"], default="matn",
                   help="chiqish formati (sukut: matn)")
    p.add_argument("--chiqish", type=Path, metavar="YO'L",
                   help="natijani faylga yozish (sukut: ekranga)")
    p.add_argument("--max-baho", type=int, metavar="N",
                   help="eng katta ruxsat etilgan baho (sukut: 100)")
    p.add_argument("--qatiy", action="store_true",
                   help="bitta xato qator ham butun ishni to'xtatsin")
    p.add_argument("-j", "--ishchilar", type=int, default=4, metavar="N",
                   help="parallel ishchilar soni (sukut: 4)")
    p.add_argument("-v", "--batafsil", action="count", default=0,
                   help="-v: INFO, -vv: DEBUG")
    p.add_argument("-q", "--jim", action="store_true", help="faqat xatolarni ko'rsat")
    p.add_argument("--version", action="version", version=f"hisobot {__version__}")
    return p


def main(argv: list[str] | None = None) -> int:
    args = tahlilchi().parse_args(argv)
    jurnalni_sozla(args.batafsil, args.jim)

    try:
        sozlama = Sozlama.muhitdan()
        if args.max_baho is not None:
            sozlama = Sozlama(max_baho=args.max_baho, min_baho=sozlama.min_baho,
                              chiqish_papka=sozlama.chiqish_papka, qatiy=args.qatiy or sozlama.qatiy)
            sozlama.tekshir()
    except SozlamaXato as e:
        log.error("sozlama xatosi: %s", e)
        return 2

    natijalar = hammasi(list(args.fayllar), ishchilar=args.ishchilar)
    xatolar_bor = False
    birlashgan = None

    for nom in sorted(natijalar):
        n = natijalar[nom]
        if isinstance(n, str):
            log.error("%s: %s", nom, n)
            xatolar_bor = True
            continue
        if birlashgan is None:
            birlashgan = n
        else:
            birlashgan.talabalar.extend(n.talabalar)
            birlashgan.xatolar.extend(n.xatolar)

    if birlashgan is None:
        log.error("hech qaysi fayl o'qilmadi")
        return 1
    if birlashgan.xatolar:
        xatolar_bor = True
        if sozlama.qatiy:
            log.error("qat'iy rejim: %d xato qator topildi", len(birlashgan.xatolar))
            return 1

    if args.format == "json":
        if args.chiqish:
            json_yoz(birlashgan, args.chiqish)
        else:
            import json
            print(json.dumps(xulosa(birlashgan), ensure_ascii=False, indent=2))
    elif args.format == "csv":
        if not args.chiqish:
            log.error("--format csv uchun --chiqish kerak")
            return 2
        csv_yoz(birlashgan, args.chiqish)
    else:
        chop_et(birlashgan)

    return 1 if xatolar_bor else 0


def chop_et(natija) -> None:
    print(f"jami: {natija.jami}  to'g'ri: {len(natija.talabalar)}  xato: {len(natija.xatolar)}")
    print("\nguruh bo'yicha o'rtacha:")
    for g, o in guruh_ortachasi(natija.talabalar).items():
        print(f"  {g}: {o}")
    print("\ndarajalar:")
    for d, n in daraja_taqsimoti(natija.talabalar).items():
        print(f"  {d}: {n}")
    eng = eng_yaxshi(natija.talabalar)
    if eng is not None:
        print(f"\neng yuqori: {eng.ism} ({eng.baho}, {eng.guruh})")
    for qator, ism, sabab in natija.xatolar:
        print(f"  {qator}-qator ({ism}): {sabab}", file=sys.stderr)


if __name__ == "__main__":
    sys.exit(main())

shu faylni yaratingsrc/hisobot/__main__.py

python7 qator
"""python3 -m hisobot bilan ishga tushadi."""
import sys

from .cli import main

if __name__ == "__main__":
    sys.exit(main())
matn1 qator
.venv/bin/python3 -m hisobot --help
Chiqish (qaytish kodi 0, 130 ms)
usage: hisobot [-h] [--format {matn,json,csv}] [--chiqish YO'L] [--max-baho N]
               [--qatiy] [-j N] [-v] [-q] [--version]
               FAYL [FAYL ...]

CSV fayldan talabalar hisobotini tayyorlaydi.

positional arguments:
  FAYL                  bir yoki bir nechta CSV fayl

options:
  -h, --help            show this help message and exit
  --format {matn,json,csv}
                        chiqish formati (sukut: matn)
  --chiqish YO'L        natijani faylga yozish (sukut: ekranga)
  --max-baho N          eng katta ruxsat etilgan baho (sukut: 100)
  --qatiy               bitta xato qator ham butun ishni to'xtatsin
  -j N, --ishchilar N   parallel ishchilar soni (sukut: 4)
  -v, --batafsil        -v: INFO, -vv: DEBUG
  -q, --jim             faqat xatolarni ko'rsat
  --version             show program's version number and exit

Misol: hisobot talabalar.csv --format json --chiqish natija.json

Yordam matni avtomatik yasaldi. metavar, help va epilog — hammasi shu yerda ko'rinadi.

Sinab ko'ramiz

python3 qator
echo "=== 1) oddiy ishlatish ==="
.venv/bin/python3 -m hisobot sinov_malumot.csv
echo "qaytish kodi: $?"
Chiqish (qaytish kodi 0, 115 ms)
=== 1) oddiy ishlatish ===
jami: 8  to'g'ri: 6  xato: 2

guruh bo'yicha o'rtacha:
  A: 90.67
  B: 83.0
  C: 61.0

darajalar:
  yaxshi: 3
  a'lo: 2
  qoniqarli: 1

eng yuqori: Feruza (95, A)
qaytish kodi: 1
WARNING  hisobot.oqish: 8 qatordan 2 tasi rad etildi
  5-qator (Elyor): baho son emas: ''
  8-qator (Hasan): baho son emas: 'abc'
python2 qator
echo "=== 2) JSON format ==="
.venv/bin/python3 -m hisobot sinov_malumot.csv --format json --jim
Chiqish (qaytish kodi 1, 114 ms)
=== 2) JSON format ===
{
  "jami": 8,
  "togri": 6,
  "xato": 2,
  "guruh_ortachasi": {
    "A": 90.67,
    "B": 83.0,
    "C": 61.0
  },
  "darajalar": {
    "yaxshi": 3,
    "a'lo": 2,
    "qoniqarli": 1
  },
  "xatolar": [
    {
      "qator": 5,
      "ism": "Elyor",
      "sabab": "baho son emas: ''"
    },
    {
      "qator": 8,
      "ism": "Hasan",
      "sabab": "baho son emas: 'abc'"
    }
  ]
}
python7 qator
echo "=== 3) bir nechta fayl ==="
.venv/bin/python3 -m hisobot ko_p_sinov/f1.csv ko_p_sinov/f2.csv ko_p_sinov/f3.csv --jim
echo "qaytish kodi: $?"
echo ""
echo "=== 4) --max-baho bilan (5 balli tizim) ==="
.venv/bin/python3 -m hisobot sinov_malumot.csv --max-baho 5 --jim 2>&1 | head -12
echo "qaytish kodi: ${PIPESTATUS[0]}"
Chiqish (qaytish kodi 0, 481 ms)
=== 3) bir nechta fayl ===
jami: 6  to'g'ri: 6  xato: 0

guruh bo'yicha o'rtacha:
  A: 82.0
  B: 92.0

darajalar:
  yaxshi: 3
  a'lo: 3

eng yuqori: X3 (93, B)
qaytish kodi: 0

=== 4) --max-baho bilan (5 balli tizim) ===
  5-qator (Elyor): baho son emas: ''
  8-qator (Hasan): baho son emas: 'abc'
jami: 8  to'g'ri: 6  xato: 2

guruh bo'yicha o'rtacha:
  A: 90.67
  B: 83.0
  C: 61.0

darajalar:
  yaxshi: 3
  a'lo: 2
qaytish kodi: 1
python6 qator
echo "=== 5) noto'g'ri argument ==="
.venv/bin/python3 -m hisobot sinov_malumot.csv --format xml
echo "qaytish kodi: $?"
echo ""
echo "=== 6) versiya ==="
.venv/bin/python3 -m hisobot --version
Chiqish (qaytish kodi 0, 327 ms)
=== 5) noto'g'ri argument ===
qaytish kodi: 2

=== 6) versiya ===
hisobot 0.4.0
usage: hisobot [-h] [--format {matn,json,csv}] [--chiqish YO'L] [--max-baho N]
               [--qatiy] [-j N] [-v] [-q] [--version]
               FAYL [FAYL ...]
hisobot: error: argument --format: invalid choice: 'xml' (choose from 'matn', 'json', 'csv')

argparse noto'g'ri --format ni o'zi rad etdi va qaytish kodi 2 berdi — bu CLI ning standart konvensiyasi.

Qaytish kodlari

python7 qator
import pandas as pd
show(pd.DataFrame([
    {"kod": 0, "ma'nosi": "Muvaffaqiyat", "bizda": "hamma qator to'g'ri"},
    {"kod": 1, "ma'nosi": "Umumiy xato", "bizda": "xato qatorlar bor yoki fayl o'qilmadi"},
    {"kod": 2, "ma'nosi": "Noto'g'ri ishlatish", "bizda": "yomon argument yoki sozlama"},
    {"kod": 130, "ma'nosi": "Ctrl+C bilan to'xtatilgan", "bizda": "Python o'zi qaytaradi"},
]))
kodma'nosibizda
0Muvaffaqiyathamma qator to'g'ri
1Umumiy xatoxato qatorlar bor yoki fayl o'qilmadi
2Noto'g'ri ishlatishyomon argument yoki sozlama
130Ctrl+C bilan to'xtatilganPython o'zi qaytaradi

Nega bu muhim: skriptlar qaytish kodiga qarab qaror qiladi.

python11 qator
if .venv/bin/python3 -m hisobot ko_p_sinov/f1.csv --jim > /dev/null 2>&1; then
  echo "f1.csv: toza o'tdi -> keyingi qadamga o'tish mumkin"
else
  echo "f1.csv: muammo bor -> to'xtaymiz"
fi

if .venv/bin/python3 -m hisobot sinov_malumot.csv --jim > /dev/null 2>&1; then
  echo "sinov_malumot.csv: toza o'tdi"
else
  echo "sinov_malumot.csv: muammo bor (xato qatorlar) -> tekshirish kerak"
fi
Chiqish (qaytish kodi 0, 272 ms)
f1.csv: toza o'tdi -> keyingi qadamga o'tish mumkin
sinov_malumot.csv: muammo bor (xato qatorlar) -> tekshirish kerak

stdout va stderr ni ajratish

python6 qator
echo "=== natija faylga, xatolar ekranga ==="
.venv/bin/python3 -m hisobot sinov_malumot.csv --format json > natija2.json
echo "--- fayl ichida faqat JSON bor: ---"
head -5 natija2.json
.venv/bin/python3 -c "import json; json.load(open('natija2.json')); print('JSON to\'g\'ri tahlil qilindi')"
rm natija2.json
Chiqish (qaytish kodi 0, 166 ms)
=== natija faylga, xatolar ekranga ===
--- fayl ichida faqat JSON bor: ---
{
  "jami": 8,
  "togri": 6,
  "xato": 2,
  "guruh_ortachasi": {
JSON to'g'ri tahlil qilindi
WARNING  hisobot.oqish: 8 qatordan 2 tasi rad etildi

Natija faylga toza JSON bo'lib tushdi, ogohlantirishlar esa ekranga chiqdi. Agar jurnal stdout ga yozilganda, JSON buzilardi.

Paketni o'rnatiladigan qilamiz

Hozir python3 -m hisobot deb chaqiramiz. Foydalanuvchi shunchaki hisobot deb yozishi kerak.

shu faylni yaratingpyproject.toml

python48 qator
[project]
name = "hisobot"
version = "1.0.0"
description = "CSV ma'lumotini tekshirib hisobot chiqaradigan asbob"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
authors = [{name = "Kurs talabasi"}]
keywords = ["csv", "hisobot", "tahlil"]
classifiers = [
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
]
dependencies = []

[project.optional-dependencies]
dev = [
    "pytest>=8",
    "pytest-cov>=5",
    "mypy>=1.8",
    "ruff>=0.5",
]

[project.scripts]
hisobot = "hisobot.cli:main"

[project.urls]
Homepage = "https://example.uz/hisobot"

[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-q --strict-markers"

[tool.coverage.run]
source = ["hisobot"]

[tool.coverage.report]
show_missing = true
exclude_lines = [
    "pragma: no cover",
    "if __name__ == .__main__.:",
    "raise NotImplementedError",
]

shu faylni yaratingREADME.md

matn5 qator
# hisobot

CSV fayldan talabalar hisobotini tayyorlaydigan buyruq qatori asbobi.

## O'rnatish

pip install -e ".[dev]"

matn1 qator
## Ishlatish

hisobot talabalar.csv hisobot talabalar.csv --format json --chiqish natija.json hisobot *.csv -j 8 -v

matn3 qator
## Sozlash

Muhit o'zgaruvchilari: `HISOBOT_MAX_BAHO`, `HISOBOT_MIN_BAHO`, `HISOBOT_QATIY`.
matn4 qator
.venv/bin/python3 -m pip install --quiet -e .
ls .venv/bin/ | grep hisobot
.venv/bin/hisobot --version
.venv/bin/hisobot sinov_malumot.csv --jim
Chiqish (qaytish kodi 1, 23417 ms)
hisobot
hisobot 0.4.0
jami: 8  to'g'ri: 6  xato: 2

guruh bo'yicha o'rtacha:
  A: 90.67
  B: 83.0
  C: 61.0

darajalar:
  yaxshi: 3
  a'lo: 2
  qoniqarli: 1

eng yuqori: Feruza (95, A)
  5-qator (Elyor): baho son emas: ''
  8-qator (Hasan): baho son emas: 'abc'

[project.scripts] bo'limi hisobot nomli bajariladigan fayl yasadi. Endi python3 -m shart emas.

main() ni sinash

CLI ni sinashning to'g'ri usuli — main() ni to'g'ridan-to'g'ri chaqirish, jarayon ishga tushirmasdan.

shu faylni yaratingtests/test_cli.py

python66 qator
import json

import pytest

from hisobot.cli import main

TOGRI = "ism,guruh,baho,sana\nAziz,A,85,2026-09-01\nBobur,B,95,2026-09-02\n"
XATOLI = "ism,guruh,baho,sana\nAziz,A,abc,2026-09-01\n"


def _fayl(papka, nom, matn):
    y = papka / nom
    y.write_text(matn, encoding="utf-8")
    return str(y)


def test_toza_fayl_nol_qaytaradi(tmp_path, capsys):
    kod = main([_fayl(tmp_path, "a.csv", TOGRI), "--jim"])
    assert kod == 0
    assert "to'g'ri: 2" in capsys.readouterr().out


def test_xatoli_fayl_bir_qaytaradi(tmp_path):
    assert main([_fayl(tmp_path, "b.csv", XATOLI), "--jim"]) == 1


def test_fayl_yoq_bir_qaytaradi(tmp_path):
    assert main([str(tmp_path / "yoq.csv"), "--jim"]) == 1


def test_json_format(tmp_path, capsys):
    main([_fayl(tmp_path, "c.csv", TOGRI), "--format", "json", "--jim"])
    malumot = json.loads(capsys.readouterr().out)
    assert malumot["togri"] == 2
    assert malumot["guruh_ortachasi"] == {"A": 85.0, "B": 95.0}


def test_json_faylga(tmp_path):
    chiqish = tmp_path / "n.json"
    main([_fayl(tmp_path, "d.csv", TOGRI), "--format", "json", "--chiqish", str(chiqish), "--jim"])
    assert json.loads(chiqish.read_text(encoding="utf-8"))["togri"] == 2


def test_csv_chiqishsiz_ikki_qaytaradi(tmp_path):
    assert main([_fayl(tmp_path, "e.csv", TOGRI), "--format", "csv", "--jim"]) == 2


def test_max_baho_cheklovi(tmp_path):
    """85 va 95 — 5 balli tizimda ikkalasi ham xato."""
    assert main([_fayl(tmp_path, "f.csv", TOGRI), "--max-baho", "5", "--jim"]) == 1


def test_notogri_max_baho(tmp_path):
    assert main([_fayl(tmp_path, "g.csv", TOGRI), "--max-baho", "-5", "--jim"]) == 2


def test_bir_nechta_fayl(tmp_path, capsys):
    kod = main([_fayl(tmp_path, "h1.csv", TOGRI), _fayl(tmp_path, "h2.csv", TOGRI), "--jim"])
    assert kod == 0
    assert "jami: 4" in capsys.readouterr().out


def test_notogri_argument_sistemexit(tmp_path):
    with pytest.raises(SystemExit) as e:
        main([_fayl(tmp_path, "i.csv", TOGRI), "--format", "xml"])
    assert e.value.code == 2
matn1 qator
.venv/bin/python3 -m pytest tests/test_cli.py -q 2>&1 | tail -12
Chiqish (qaytish kodi 0, 3800 ms)
guruh bo'yicha o'rtacha:
  A: 85.0
  B: 95.0

darajalar:
  yaxshi: 1
  a'lo: 1

eng yuqori: Bobur (95, B)
=========================== short test summary info ============================
FAILED tests/test_cli.py::test_max_baho_cheklovi - AssertionError: assert 0 == 1

Test o'lik bayroqni topdi

test_max_baho_cheklovi yiqildi: --max-baho 5 berilgan, lekin qaytish kodi 1 emas, 0 chiqdi. Yuqoridagi 4-misolga qayting — u yerda ham --max-baho 5 bilan natija o'zgarmagan edi, 85 baho baribir qabul qilindi.

Sabab kodda ko'rinib turibdi: main() sozlama obyektini yasaydi, keyin uni hech kimga bermaydi. hammasi() -> tozala() -> bahoni_tekshir() zanjiri modul darajasidagi ODATIY sozlamani ishlatadi.

Ya'ni bayroq bor, yordam matnida ko'rinadi, lekin hech narsa qilmaydi. Bu eng yomon turdagi xato: foydalanuvchi uni ishlatadi va natijaga ishonadi.

11-bobdagi qoida aynan shu holat uchun edi: sozlamani argument sifatida uzating. Endi zanjirni to'liq ulaymiz — uch faylga tegamiz.

shu faylni yaratingsrc/hisobot/oqish.py

python60 qator
"""CSV o'qish va qatorlarni tekshirib modelga aylantirish."""
import csv
import logging
from pathlib import Path

from .modellar import Natija, Talaba
from .sozlama import Sozlama
from .tekshiruv import bahoni_tekshir, sanani_tekshir
from .xatolar import FaylXato

log = logging.getLogger(__name__)

KERAKLI_USTUNLAR = {"ism", "guruh", "baho", "sana"}


def oqi(yol: str | Path) -> list[dict[str, str]]:
    """CSV ni o'qiydi. Har qanday muammoda FaylXato qaytaradi."""
    p = Path(yol)
    log.debug("fayl o'qilmoqda: %s", p)
    try:
        matn = p.read_text(encoding="utf-8")
    except FileNotFoundError:
        raise FaylXato(str(p), "fayl topilmadi") from None
    except PermissionError:
        raise FaylXato(str(p), "o'qishga ruxsat yo'q") from None
    except UnicodeDecodeError as e:
        raise FaylXato(str(p), f"matn UTF-8 emas ({e.reason})") from None

    qatorlar = list(csv.DictReader(matn.splitlines()))
    if not qatorlar:
        raise FaylXato(str(p), "fayl bo'sh yoki faqat sarlavha bor")
    yetishmayotgan = KERAKLI_USTUNLAR - set(qatorlar[0])
    if yetishmayotgan:
        raise FaylXato(str(p), f"ustunlar yetishmaydi: {sorted(yetishmayotgan)}")
    log.info("o'qildi: %d qator, fayl %s", len(qatorlar), p.name)
    return qatorlar


def tozala(qatorlar: list[dict[str, str]], sozlama: Sozlama | None = None) -> Natija:
    """Xom lug'atlarni Talaba obyektlariga aylantiradi. Xatolar to'planadi, otilmaydi."""
    natija = Natija()
    for i, q in enumerate(qatorlar, start=2):
        try:
            baho = bahoni_tekshir(q.get("baho"), sozlama)
            sana = sanani_tekshir(q.get("sana"))
        except ValueError as e:
            log.debug("%d-qator rad etildi: %s", i, e)
            natija.xatolar.append((i, q.get("ism", "?"), str(e)))
            continue
        natija.talabalar.append(
            Talaba(
                ism=q.get("ism", "").strip(),
                guruh=q.get("guruh", "").strip(),
                baho=baho,
                sana=sana,
            )
        )
    if natija.xatolar:
        log.warning("%d qatordan %d tasi rad etildi", natija.jami, len(natija.xatolar))
    return natija

shu faylni yaratingsrc/hisobot/ko_p.py

python33 qator
"""Bir nechta CSV faylni parallel qayta ishlash."""
import logging
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path

from .modellar import Natija
from .oqish import oqi, tozala
from .sozlama import Sozlama
from .xatolar import HisobotXato

log = logging.getLogger(__name__)


def bitta(yol: Path, sozlama: Sozlama | None = None) -> tuple[Path, Natija | None, str | None]:
    """Bitta faylni qayta ishlaydi. Xato otmaydi — uni qaytaradi."""
    try:
        return yol, tozala(oqi(yol), sozlama), None
    except HisobotXato as e:
        return yol, None, str(e)


def hammasi(
    yollar: list[Path], ishchilar: int = 4, sozlama: Sozlama | None = None
) -> dict[str, Natija | str]:
    """Fayllarni parallel qayta ishlaydi. Bitta faylning xatosi boshqalarni to'xtatmaydi."""
    natijalar: dict[str, Natija | str] = {}
    with ThreadPoolExecutor(max_workers=ishchilar) as p:
        kelajaklar = {p.submit(bitta, y, sozlama): y for y in yollar}
        for k in as_completed(kelajaklar):
            yol, natija, xato = k.result()
            natijalar[yol.name] = natija if natija is not None else f"XATO: {xato}"
            log.debug("tugadi: %s", yol.name)
    return natijalar

cli.py da esa bitta chaqiruvga sozlama=sozlama qo'shiladi:

python1 qator
natijalar = hammasi(list(args.fayllar), ishchilar=args.ishchilar, sozlama=sozlama)
python2 qator
sed -i 's/hammasi(list(args.fayllar), ishchilar=args.ishchilar)/hammasi(list(args.fayllar), ishchilar=args.ishchilar, sozlama=sozlama)/' src/hisobot/cli.py
grep -n "hammasi(list" src/hisobot/cli.py
Chiqish (qaytish kodi 0, 41 ms)
66:    natijalar = hammasi(list(args.fayllar), ishchilar=args.ishchilar, sozlama=sozlama)
python4 qator
.venv/bin/python3 -m pytest tests/test_cli.py -q 2>&1 | tail -4
echo ""
echo "=== endi --max-baho haqiqatan ishlaydi ==="
.venv/bin/python3 -m hisobot sinov_malumot.csv --max-baho 5 --jim 2>&1 | head -8
Chiqish (qaytish kodi 0, 2072 ms)
..........                                                               [100%]

=== endi --max-baho haqiqatan ishlaydi ===
  2-qator (Aziz): baho 0..5 oralig'ida bo'lishi kerak, berilgan: 85
  3-qator (Bobur): baho 0..5 oralig'ida bo'lishi kerak, berilgan: 92
  4-qator (Dilnoza): baho 0..5 oralig'ida bo'lishi kerak, berilgan: 78
  5-qator (Elyor): baho son emas: ''
  6-qator (Feruza): baho 0..5 oralig'ida bo'lishi kerak, berilgan: 95
  7-qator (Gulnora): baho 0..5 oralig'ida bo'lishi kerak, berilgan: 61
  8-qator (Hasan): baho son emas: 'abc'
  9-qator (Iroda): baho 0..5 oralig'ida bo'lishi kerak, berilgan: 88

Endi --max-baho 5 haqiqiy ta'sir qiladi: 85 va 95 baholar ham rad etiladi.

:::tip Bayroq qo'shganda uning ta'sirini sinang "Bayroq bor, lekin ishlamaydi" — bu xato turini faqat test topadi. Qo'lda sinasangiz, chiqishga yuzaki qarab "ishlayotganga o'xshaydi" deb o'tib ketish juda oson. Har yangi parametr uchun uning natijaga ta'sirini tekshiradigan test yozing, shunchaki mavjudligini emas. :::

capsys — pytest fixture i, u stdout/stderr ni ushlaydi. main() ni chaqirish jarayon ishga tushirishdan yuz barobar tezroq va xato izini to'g'ridan-to'g'ri ko'rsatadi.

:::tip main(argv) naqshi main() ni argv argumenti bilan yozing va sys.argv ni faqat None bo'lganda o'qing. Shunda test uni istalgan argumentlar bilan chaqira oladi. sys.exit() ni esa faqat if __name__ == "__main__" blokida chaqiring — main() ning o'zi son qaytarsin. :::

Paketni yig'ish

matn3 qator
.venv/bin/python3 -m pip install --quiet build
.venv/bin/python3 -m build --wheel --outdir dist_sinov 2>&1 | tail -5
ls -la dist_sinov/
Chiqish (qaytish kodi 0, 17181 ms)
        See https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license for details.
        ********************************************************************************

!!
Successfully built hisobot-1.0.0-py3-none-any.whl
total 12
drwxrwxrwx 1 abulqosim abulqosim   512 Sep 29 14:01 .
drwxrwxrwx 1 abulqosim abulqosim   512 Sep 29 14:01 ..
-rwxrwxrwx 1 abulqosim abulqosim 11360 Sep 29 14:01 hisobot-1.0.0-py3-none-any.whl
python10 qator
.venv/bin/python3 -c "
import zipfile, glob
w = glob.glob('dist_sinov/*.whl')[0]
with zipfile.ZipFile(w) as z:
    nomlar = sorted(z.namelist())
print('wheel ichidagi fayllar:')
for n in nomlar:
    print('  ', n)
"
rm -rf dist_sinov
Chiqish (qaytish kodi 0, 336 ms)
wheel ichidagi fayllar:
   hisobot-1.0.0.dist-info/METADATA
   hisobot-1.0.0.dist-info/RECORD
   hisobot-1.0.0.dist-info/WHEEL
   hisobot-1.0.0.dist-info/entry_points.txt
   hisobot-1.0.0.dist-info/top_level.txt
   hisobot/__init__.py
   hisobot/__main__.py
   hisobot/chiqarish.py
   hisobot/cli.py
   hisobot/hisob.py
   hisobot/ko_p.py
   hisobot/modellar.py
   hisobot/muhit.py
   hisobot/oqish.py
   hisobot/sozlama.py
   hisobot/tarmoq.py
   hisobot/tekshiruv.py
   hisobot/xatolar.py

E'tibor bering: wheel ichida testlar yo'q — src/ tartibi tufayli faqat paket kirdi. Bu to'g'ri: foydalanuvchiga testlar kerak emas.

Xulosa

Tushuncha Asosiy fikr
argparse Yordam matni avtomatik; choices noto'g'ri qiymatni o'zi rad etadi
Qaytish kodi 0 muvaffaqiyat, 1 xato, 2 noto'g'ri ishlatish
stdout / stderr Natija stdout ga, jurnal stderr ga — quvurda ishlashi uchun
main(argv) sys.argv ni faqat None da o'qing; sys.exit ni tashqarida chaqiring
[project.scripts] hisobot buyrug'ini yasaydi
capsys Testda chiqishni ushlaydi
Wheel src/ tartibi tufayli testlar paketga tushmaydi

Talaba ishi

  1. --chiqish berilgan, lekin papka mavjud emas bo'lsa nima bo'ladi? Tushunarli xato bilan qaytish kodi 2 bering.
  2. --format ga markdown variantini qo'shing. Nechta joyga tegish kerak bo'ldi?
  3. hisobot ni --qatiy bilan ishga tushiring. Qaytish kodi va chiqish qanday farq qiladi?
  4. argparse o'rniga typer bilan qayta yozing. Kod qisqardimi? Yordam matni qanday ko'rinadi?

Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar