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
Tirsvad bcea547c8b Merge pull request 'Milestone 1: Project scaffold' (#13) from milestone/01-scaffold into main
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 6s
Reviewed-on: #13
2026-10-04 07:54:59 +02:00
Tirsvad ffdf64400f feat: scaffold project structure and add configuration files
- Add src/secret_auction package with __init__.py and constants.py
- Configure pyproject.toml with setuptools, pytest, and console script
- Add Doxyfile and .env.example for documentation and environment setup
- Update .gitignore to exclude Doxygen output directory
- Include tests directory placeholder and docs/plan.md for milestone
  tracking
2026-10-04 13:53:07 +08:00
15 changed files with 385 additions and 2 deletions
+3
View File
@@ -174,3 +174,6 @@ cython_debug/
# PyPI configuration file
.pypirc
# Doxygen output
docs/doxygen/
+12
View File
@@ -0,0 +1,12 @@
PROJECT_NAME = "Secret Auction"
PROJECT_BRIEF = "A blind auction command line program"
OUTPUT_DIRECTORY = docs/doxygen
INPUT = src README.md
FILE_PATTERNS = *.py *.md
RECURSIVE = YES
OPTIMIZE_OUTPUT_JAVA = YES
EXTRACT_ALL = YES
USE_MDFILE_AS_MAINPAGE = README.md
GENERATE_HTML = YES
GENERATE_LATEX = NO
QUIET = YES
+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

+40
View File
@@ -0,0 +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`)
- [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`)
- [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
+26
View File
@@ -0,0 +1,26 @@
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "secret-auction"
version = "0.1.0"
description = "A beginner-friendly blind auction program (Udemy 100 Days of Code, Day 9)."
readme = "README.md"
requires-python = ">=3.13"
license = "AGPL-3.0-or-later"
license-files = ["LICENSE"]
dependencies = []
[project.optional-dependencies]
dev = ["pytest>=8"]
[project.scripts]
secret-auction = "secret_auction.main:main"
[tool.setuptools.packages.find]
where = ["src"]
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
+5
View File
@@ -0,0 +1,5 @@
"""! @file __init__.py
@brief Secret Auction package: a blind auction command line program.
"""
__version__ = "0.1.0"
+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
View File
@@ -0,0 +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")
## 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()
View File
+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