Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
058c2451e0 | ||
|
|
e73218d9f4 |
+40
-40
@@ -1,40 +1,40 @@
|
||||
# Plan: Secret Auction Program (Day 9)
|
||||
|
||||
## Context
|
||||
|
||||
Udemy 100 Days of Code blind-auction CLI. The repository uses a Python `.gitignore`, an AGPL `LICENSE` and a git-ignored `.env` (tokens, never printed or committed). Python 3.13 or newer is required.
|
||||
|
||||
## Working method
|
||||
|
||||
Work on ONE milestone at a time. Each milestone is one branch (`milestone/NN-name`) and one pull request. When a milestone is finished, stop, verify it and review before starting the next. Milestones and issues are also tracked on the Gitea repository (via API, token from `.env`).
|
||||
|
||||
## Milestone 1 - Project scaffold and repo metadata (`milestone/01-scaffold`)
|
||||
|
||||
- [x] Create `src/secret_auction/__init__.py`, `tests/`, `docs/`
|
||||
- [x] `pyproject.toml`: `requires-python >=3.13`, src layout, no runtime deps, `dev` extra (`pytest`), pytest config, console script
|
||||
- [x] `.env.example`; `.env` and `.venv/` ignored; add `docs/doxygen/` to `.gitignore`
|
||||
- [x] `constants.py`
|
||||
- [x] `Doxyfile`
|
||||
- [x] Set repository description and topics (`python`, `udemy`, `100-days-of-code`, `blind-auction`, `beginner`) via API
|
||||
- [x] Create milestones and issues on the host (milestones M1-M4, issues #1-#12)
|
||||
- [x] Verify: create `.venv`, upgrade pip, `pip install -e ".[dev]"`
|
||||
- [x] Save plan and milestones in `docs/plan.md`
|
||||
|
||||
## Milestone 2 - Core auction logic (`milestone/02-core-logic`)
|
||||
|
||||
- [x] `auction.py` (Doxygen comments): `add_bid`, `find_winner` (uses `max(bids, key=bids.get)`, `ValueError` on empty)
|
||||
- [x] `tests/test_auction.py`
|
||||
- Verify: `pytest` passes
|
||||
|
||||
## Milestone 3 - CLI program (`milestone/03-cli`)
|
||||
|
||||
- [ ] `main.py`: prompt name and numeric bid (re-prompt on invalid), bids dict kept outside the loop, "other bidders? yes/no", clear screen with newlines between bidders, announce the winner
|
||||
- [ ] `tests/test_main.py` with monkeypatched `input`
|
||||
- Verify: `pytest`; scripted run `python -m secret_auction.main` with two bidders
|
||||
|
||||
## Milestone 4 - Documentation (`milestone/04-docs`)
|
||||
|
||||
- [ ] `README.md` from the template (Overview, Requirements, Setup incl. creating/activating `.venv` and `python -m pip install --upgrade pip`, Run, Tests, License AGPL, Links)
|
||||
- [ ] `docs/flowchart.md` (Mermaid plus link to the diagrams.net chart)
|
||||
- [ ] Generate Doxygen docs if `doxygen` is installed
|
||||
- Verify: README commands work verbatim; `.env` stays untracked
|
||||
# Plan: Secret Auction Program (Day 9)
|
||||
|
||||
## Context
|
||||
|
||||
Udemy 100 Days of Code blind-auction CLI. The repository uses a Python `.gitignore`, an AGPL `LICENSE` and a git-ignored `.env` (tokens, never printed or committed). Python 3.13 or newer is required.
|
||||
|
||||
## Working method
|
||||
|
||||
Work on ONE milestone at a time. Each milestone is one branch (`milestone/NN-name`) and one pull request. When a milestone is finished, stop, verify it and review before starting the next. Milestones and issues are also tracked on the Gitea repository (via API, token from `.env`).
|
||||
|
||||
## Milestone 1 - Project scaffold and repo metadata (`milestone/01-scaffold`)
|
||||
|
||||
- [x] Create `src/secret_auction/__init__.py`, `tests/`, `docs/`
|
||||
- [x] `pyproject.toml`: `requires-python >=3.13`, src layout, no runtime deps, `dev` extra (`pytest`), pytest config, console script
|
||||
- [x] `.env.example`; `.env` and `.venv/` ignored; add `docs/doxygen/` to `.gitignore`
|
||||
- [x] `constants.py`
|
||||
- [x] `Doxyfile`
|
||||
- [x] Set repository description and topics (`python`, `udemy`, `100-days-of-code`, `blind-auction`, `beginner`) via API
|
||||
- [x] Create milestones and issues on the host (milestones M1-M4, issues #1-#12)
|
||||
- [x] Verify: create `.venv`, upgrade pip, `pip install -e ".[dev]"`
|
||||
- [x] Save plan and milestones in `docs/plan.md`
|
||||
|
||||
## Milestone 2 - Core auction logic (`milestone/02-core-logic`)
|
||||
|
||||
- [x] `auction.py` (Doxygen comments): `add_bid`, `find_winner` (uses `max(bids, key=bids.get)`, `ValueError` on empty)
|
||||
- [x] `tests/test_auction.py`
|
||||
- Verify: `pytest` passes
|
||||
|
||||
## Milestone 3 - CLI program (`milestone/03-cli`)
|
||||
|
||||
- [x] `main.py`: prompt name and numeric bid (re-prompt on invalid), bids dict kept outside the loop, "other bidders? yes/no", clear screen with newlines between bidders, announce the winner
|
||||
- [x] `tests/test_main.py` with monkeypatched `input`
|
||||
- Verify: `pytest`; scripted run `python -m secret_auction.main` with two bidders
|
||||
|
||||
## Milestone 4 - Documentation (`milestone/04-docs`)
|
||||
|
||||
- [ ] `README.md` from the template (Overview, Requirements, Setup incl. creating/activating `.venv` and `python -m pip install --upgrade pip`, Run, Tests, License AGPL, Links)
|
||||
- [ ] `docs/flowchart.md` (Mermaid plus link to the diagrams.net chart)
|
||||
- [ ] Generate Doxygen docs if `doxygen` is installed
|
||||
- Verify: README commands work verbatim; `.env` stays untracked
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
"""! @file main.py
|
||||
@brief Command line interface for the secret auction program.
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
from secret_auction import constants
|
||||
from secret_auction.auction import add_bid, find_winner
|
||||
|
||||
|
||||
def clear_screen() -> None:
|
||||
"""! @brief Hide previous entries by printing blank lines."""
|
||||
print("\n" * constants.CLEAR_SCREEN_LINES)
|
||||
|
||||
|
||||
def ask_bid() -> float:
|
||||
"""! @brief Ask for a bid until a valid, non-negative number is entered.
|
||||
@return The bid amount.
|
||||
"""
|
||||
while True:
|
||||
try:
|
||||
amount = float(input(constants.PROMPT_BID))
|
||||
except ValueError:
|
||||
print(constants.MSG_INVALID_BID)
|
||||
continue
|
||||
if math.isfinite(amount) and amount >= 0:
|
||||
return amount
|
||||
print(constants.MSG_INVALID_BID)
|
||||
|
||||
|
||||
def ask_more_bidders() -> bool:
|
||||
"""! @brief Ask whether there are more bidders until yes/no is answered.
|
||||
@return True if more bidders exist, False otherwise.
|
||||
"""
|
||||
while True:
|
||||
answer = input(constants.PROMPT_MORE_BIDDERS).strip().lower()
|
||||
if answer in constants.YES_ANSWERS:
|
||||
return True
|
||||
if answer in constants.NO_ANSWERS:
|
||||
return False
|
||||
print(constants.MSG_INVALID_ANSWER)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""! @brief Run the blind auction: collect bids, then announce the winner."""
|
||||
print(constants.MSG_WELCOME)
|
||||
bids: dict[str, float] = {} # kept outside the loop so bids are not lost
|
||||
more_bidders = True
|
||||
while more_bidders:
|
||||
name = input(constants.PROMPT_NAME).strip()
|
||||
add_bid(bids, name, ask_bid())
|
||||
more_bidders = ask_more_bidders()
|
||||
if more_bidders:
|
||||
clear_screen()
|
||||
winner, amount = find_winner(bids)
|
||||
print(constants.MSG_WINNER.format(name=winner, amount=amount))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,35 @@
|
||||
from secret_auction import constants
|
||||
from secret_auction.main import ask_bid, ask_more_bidders, main
|
||||
|
||||
|
||||
def feed(monkeypatch, answers):
|
||||
it = iter(answers)
|
||||
monkeypatch.setattr("builtins.input", lambda _prompt="": next(it))
|
||||
|
||||
|
||||
def test_ask_bid_reprompts_on_invalid(monkeypatch, capsys):
|
||||
feed(monkeypatch, ["abc", "-5", "nan", "12.5"])
|
||||
assert ask_bid() == 12.5
|
||||
assert capsys.readouterr().out.count(constants.MSG_INVALID_BID) == 3
|
||||
|
||||
|
||||
def test_ask_more_bidders(monkeypatch, capsys):
|
||||
feed(monkeypatch, ["maybe", "Y"])
|
||||
assert ask_more_bidders() is True
|
||||
assert constants.MSG_INVALID_ANSWER in capsys.readouterr().out
|
||||
feed(monkeypatch, ["no"])
|
||||
assert ask_more_bidders() is False
|
||||
|
||||
|
||||
def test_main_announces_highest_bidder_and_clears_screen(monkeypatch, capsys):
|
||||
feed(monkeypatch, ["Ann", "10", "yes", "Bob", "25.5", "yes", "Cy", "3", "no"])
|
||||
main()
|
||||
out = capsys.readouterr().out
|
||||
assert "The winner is Bob with a bid of $25.50." in out
|
||||
assert out.count("\n" * constants.CLEAR_SCREEN_LINES) == 2
|
||||
|
||||
|
||||
def test_main_single_bidder(monkeypatch, capsys):
|
||||
feed(monkeypatch, ["Ann", "7", "n"])
|
||||
main()
|
||||
assert "The winner is Ann with a bid of $7.00." in capsys.readouterr().out
|
||||
Reference in New Issue
Block a user