Author SHA1 Message Date
Tirsvad 6a8ab289c6 docs: complete documentation for Milestone 4
- Rewrite README.md with overview, requirements, setup, run, tests
  sections
- Add docs/flowchart.md with Mermaid diagram and link to diagrams.net
  source
- Update docs/plan.md to mark Milestone 4 as complete
2026-10-04 14:21:31 +08:00
Tirsvad 8d13657773 docs: complete documentation for Milestone 4
- Rewrite README.md with overview, requirements, setup, run, tests
  sections
- Add docs/flowchart.md with Mermaid diagram and link to diagrams.net
  source
- Update docs/plan.md to mark Milestone 4 as complete
2026-10-04 14:21:13 +08:00
Tirsvad 058c2451e0 Merge pull request 'Milestone 3: CLI program' (#15) from milestone/03-cli into main
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 4s
Reviewed-on: #15
2026-10-04 08:14:11 +02:00
Tirsvad e73218d9f4 feat: implement CLI program with user prompts and tests
- Add main.py with input prompts, bid validation, screen clearing,
  winner announcement
- Add tests/test_main.py with monkeypatched input tests
- Update docs/plan.md to mark Milestone 3 as complete
2026-10-04 14:13:01 +08:00
Tirsvad cebed68105 Merge pull request 'Milestone 2: Core auction logic' (#14) from milestone/02-core-logic into main
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 4s
Reviewed-on: #14
2026-10-04 08:09:33 +02:00
Tirsvad 4bc7012e77 refactor: remove env.py module and update plan
- Delete env.py module (functionality moved or no longer needed)
- Update docs/plan.md to reflect Milestone 2 completion
- Clean up constants.py by removing unused environment-related constants
2026-10-04 14:07:29 +08:00
Tirsvad d2cf059337 feat: implement core auction logic and env loader
- Add auction.py with add_bid and find_winner functions
- Add env.py with stdlib-based .env loader (no python-dotenv)
- Add tests/test_auction.py with test cases for auction functions
- Remove .env.example (tokens not meant to be committed)
- Update docs/plan.md to mark Milestone 2 as complete
2026-10-04 14:03:22 +08:00
11 changed files with 307 additions and 54 deletions
-4
View File
@@ -1,4 +0,0 @@
# Copy to .env and fill in. Never commit .env.
GITEA_TOKEN=
GITHUB_PAT=
GITHUB_USER=
+94 -2
View File
@@ -1,2 +1,94 @@
# 009-the_secret_auction_program
# 🚀 Secret Auction Program
A beginner-friendly Python program that runs a blind auction: bidders enter their name and bid one after another, the screen is cleared between bidders, and the highest bidder wins.
## 📚 Table of Contents
- [Overview](#-overview)
- [Requirements](#-requirements)
- [Setup](#-setup)
- [Run](#-run)
- [Tests](#-tests)
- [License](#-license)
- [Links](#-links)
## 🧭 Overview
This is the Day 9 project of Udemy's *100 Days of Code: The Complete Python Pro Bootcamp*. Each bidder enters a name and a bid, which are stored in a dictionary (name → bid). After each bidder the program asks whether there are more bidders and, if so, clears the screen so the next bidder cannot see earlier bids. When everyone has bid, the program announces the winner.
See the [program flowchart](docs/flowchart.md) and the [plan and milestones](docs/plan.md).
### 🖼️ Screenshots
Entering a bid:
![Entering a bid](docs/images/bidding.png)
Announcing the winner:
![Announcing the winner](docs/images/winner.png)
## 📋 Requirements
- Python 3.13 or newer
- No runtime dependencies
- `pytest` for the tests (installed by the `dev` extra)
- [Doxygen](https://www.doxygen.nl/) (optional, to generate the API documentation)
## 🛠️ Setup
Create and activate a local virtual environment, upgrade pip, then install the project with its development tools.
Windows (PowerShell):
```powershell
python -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
Linux / macOS:
```bash
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
## ▶️ Run
With the virtual environment active:
```bash
python -m secret_auction.main
```
or use the installed command:
```bash
secret-auction
```
## 🧪 Tests
```bash
python -m pytest
```
To generate the API documentation (output in `docs/doxygen/`):
```bash
doxygen Doxyfile
```
## 📄 License
Licensed under the GNU Affero General Public License v3.0 — see [LICENSE](LICENSE).
## 🔗 Links
- [Repository](https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/009-the_secret_auction_program)
- [Documentation](docs/)
- [Issue tracker](https://git.tirsystem.com/Tirsvad-Udemy-100_days_of_code/009-the_secret_auction_program/issues)
+17
View File
@@ -0,0 +1,17 @@
# Blind Auction Flowchart
Original chart: [diagrams.net](https://viewer.diagrams.net/?highlight=0000ff&edit=_blank&layers=1&nav=1&title=Blind%20Auction%20Flow%20Chart)
```mermaid
flowchart TD
A([Start]) --> B[Print welcome message]
B --> C[bids = empty dictionary]
C --> D[/Ask name/]
D --> E[/Ask bid, repeat until valid/]
E --> F[Store bids name = bid]
F --> G{Other bidders?}
G -- yes --> H[Clear screen] --> D
G -- no --> I[Find highest bid]
I --> J[/Print winner/]
J --> K([End])
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+8 -9
View File
@@ -20,22 +20,21 @@ Work on ONE milestone at a time. Each milestone is one branch (`milestone/NN-nam
- [x] Verify: create `.venv`, upgrade pip, `pip install -e ".[dev]"`
- [x] Save plan and milestones in `docs/plan.md`
## Milestone 2 - Core auction logic and env loader (`milestone/02-core-logic`)
## Milestone 2 - Core auction logic (`milestone/02-core-logic`)
- [ ] `auction.py` (Doxygen comments): `add_bid`, `find_winner` (uses `max(bids, key=bids.get)`, `ValueError` on empty)
- [ ] `env.py`: stdlib `.env` loader (no python-dotenv); does not override existing environment variables
- [ ] `tests/test_auction.py`, `tests/test_env.py`
- [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`
- [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
- [x] `README.md` from the template (Overview, Requirements, Setup incl. creating/activating `.venv` and `python -m pip install --upgrade pip`, Run, Tests, License AGPL, Links)
- [x] `docs/flowchart.md` (Mermaid plus link to the diagrams.net chart)
- [x] Generate Doxygen docs if `doxygen` is installed
- Verify: README commands work verbatim; `.env` stays untracked
+27
View File
@@ -0,0 +1,27 @@
"""! @file auction.py
@brief Core blind auction logic (no input/output).
"""
def add_bid(bids: dict[str, float], name: str, amount: float) -> None:
"""! @brief Record a bid, replacing any earlier bid by the same bidder.
@param bids Dictionary mapping bidder name to bid amount (modified in place).
@param name Name of the bidder.
@param amount Bid amount; must not be negative.
@throws ValueError If the amount is negative.
"""
if amount < 0:
raise ValueError("Bid amount must not be negative.")
bids[name] = amount
def find_winner(bids: dict[str, float]) -> tuple[str, float]:
"""! @brief Find the highest bidder.
@param bids Dictionary mapping bidder name to bid amount.
@return Tuple of (winner name, winning amount). The first bidder wins ties.
@throws ValueError If there are no bids.
"""
if not bids:
raise ValueError("No bids have been placed.")
winner = max(bids, key=bids.__getitem__)
return winner, bids[winner]
+33 -39
View File
@@ -1,39 +1,33 @@
"""! @file constants.py
@brief Constants used by the Secret Auction program.
"""
## Number of blank lines printed to hide previous bidders' entries.
CLEAR_SCREEN_LINES = 100
## Answers accepted as "yes" when asking for more bidders.
YES_ANSWERS = ("yes", "y")
## Answers accepted as "no" when asking for more bidders.
NO_ANSWERS = ("no", "n")
## Path of the file holding environment variables (tokens).
ENV_FILE = ".env"
## Name of the environment variable holding the Gitea token.
GITEA_TOKEN_ENV_VAR = "GITEA_TOKEN"
## Prompt asking for the bidder's name.
PROMPT_NAME = "What is your name? "
## Prompt asking for the bid amount.
PROMPT_BID = "What is your bid? $"
## Prompt asking whether more bidders exist.
PROMPT_MORE_BIDDERS = "Are there any other bidders? Type 'yes' or 'no'. "
## Message shown when the bid is not a valid number.
MSG_INVALID_BID = "Please enter a valid, non-negative number."
## Message shown when the yes/no answer is not understood.
MSG_INVALID_ANSWER = "Please type 'yes' or 'no'."
## Template for the winner announcement.
MSG_WINNER = "The winner is {name} with a bid of ${amount:.2f}."
## Welcome banner.
MSG_WELCOME = "Welcome to the secret auction program."
"""! @file constants.py
@brief Constants used by the Secret Auction program.
"""
## Number of blank lines printed to hide previous bidders' entries.
CLEAR_SCREEN_LINES = 100
## Answers accepted as "yes" when asking for more bidders.
YES_ANSWERS = ("yes", "y")
## Answers accepted as "no" when asking for more bidders.
NO_ANSWERS = ("no", "n")
## Prompt asking for the bidder's name.
PROMPT_NAME = "What is your name? "
## Prompt asking for the bid amount.
PROMPT_BID = "What is your bid? $"
## Prompt asking whether more bidders exist.
PROMPT_MORE_BIDDERS = "Are there any other bidders? Type 'yes' or 'no'. "
## Message shown when the bid is not a valid number.
MSG_INVALID_BID = "Please enter a valid, non-negative number."
## Message shown when the yes/no answer is not understood.
MSG_INVALID_ANSWER = "Please type 'yes' or 'no'."
## Template for the winner announcement.
MSG_WINNER = "The winner is {name} with a bid of ${amount:.2f}."
## Welcome banner.
MSG_WELCOME = "Welcome to the secret auction program."
+60
View File
@@ -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()
+33
View File
@@ -0,0 +1,33 @@
import pytest
from secret_auction.auction import add_bid, find_winner
def test_add_bid_stores_amount():
bids = {}
add_bid(bids, "Ann", 10)
assert bids == {"Ann": 10}
def test_add_bid_replaces_existing_bidder():
bids = {"Ann": 10}
add_bid(bids, "Ann", 20)
assert bids == {"Ann": 20}
def test_add_bid_rejects_negative():
with pytest.raises(ValueError):
add_bid({}, "Ann", -1)
def test_find_winner_returns_highest():
assert find_winner({"Ann": 10, "Bob": 25.5, "Cy": 3}) == ("Bob", 25.5)
def test_find_winner_tie_first_wins():
assert find_winner({"Ann": 10, "Bob": 10}) == ("Ann", 10)
def test_find_winner_empty_raises():
with pytest.raises(ValueError):
find_winner({})
+35
View File
@@ -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