MIL-001 foundation #32
@@ -0,0 +1,6 @@
|
|||||||
|
# Copy to .env and fill in. .env is ignored by git; never commit it.
|
||||||
|
SMTP_ADDRESS="smtp.gmail.com"
|
||||||
|
EMAIL_ADDRESS="you@example.com"
|
||||||
|
EMAIL_PASSWORD="Use_an_App_Password_from_your_email_provider"
|
||||||
|
# Optional: track another product instead of the default URL
|
||||||
|
# PRODUCT_URL="https://www.amazon.com/dp/B075CYMYK6"
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
# Secrets (never commit)
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.egg-info/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
|
||||||
|
# Doxygen output
|
||||||
|
docs/doxygen/
|
||||||
|
|
||||||
|
# Editors and OS
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
@@ -1,2 +1,156 @@
|
|||||||
# 047-amazon-price-tracker
|
# 🚀 Amazon Price Tracker
|
||||||
|
|
||||||
|
A Python bot for Day 47 of Udemy's "100 Days of Code: The Complete Python Pro Bootcamp": it reads a product page, extracts the price and sends an e-mail alert when the price drops below a target. Made for course participants who want to compare solutions and for visitors who browse for ideas.
|
||||||
|
|
||||||
|
## 📚 Table of Contents
|
||||||
|
|
||||||
|
- [Overview](#-overview)
|
||||||
|
- [Requirements](#-requirements)
|
||||||
|
- [Setup](#-setup)
|
||||||
|
- [Run](#-run)
|
||||||
|
- [Tests](#-tests)
|
||||||
|
- [License](#-license)
|
||||||
|
- [Links](#-links)
|
||||||
|
- [Appendix: Project documents](#-appendix-project-documents)
|
||||||
|
|
||||||
|
## 🧭 Overview
|
||||||
|
|
||||||
|
The assignment is built in four lecture steps. Each step was delivered on its own branch and pull request, and each has a screenshot and a wiki page in English, Danish and Bahasa Malaysia that explains what it teaches.
|
||||||
|
|
||||||
|
| Step | What it adds | Status |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 1 | Scrape the price from the practice page with BeautifulSoup | planned (MIL-002) |
|
||||||
|
| 2 | E-mail alert below the target price, secrets in `.env` | planned (MIL-003) |
|
||||||
|
| 3 | Browser-like request headers | planned (MIL-004) |
|
||||||
|
| 4 | The live Amazon page | planned (MIL-005) |
|
||||||
|
|
||||||
|
Repository layout:
|
||||||
|
|
||||||
|
| Path | What it is for |
|
||||||
|
| --- | --- |
|
||||||
|
| `src/amazon_price_tracker/` | The package; `constants.py` is the only place for constants |
|
||||||
|
| `tests/` | `pytest` tests; they never use the network or a real mail server |
|
||||||
|
| `docs/` | Project documents (Business Case, plan, milestones, reviews) and the wiki pages |
|
||||||
|
| `Doxyfile` | Doxygen configuration for the source comments |
|
||||||
|
| `.env.example` | The settings you put in your own `.env` |
|
||||||
|
|
||||||
|
## 📋 Requirements
|
||||||
|
|
||||||
|
- Python 3.13 or newer.
|
||||||
|
- Runtime packages (installed by `pip`): `requests`, `beautifulsoup4`, `python-dotenv`. Nothing else is needed to run the bot.
|
||||||
|
- Optional, for development: `pytest`, `ruff`, `mypy` (the `dev` extra) and [Doxygen](https://www.doxygen.nl/) for the API documentation.
|
||||||
|
|
||||||
|
## 🛠️ Setup
|
||||||
|
|
||||||
|
Create a local virtual environment named `.venv`, activate it and upgrade `pip`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m venv .venv
|
||||||
|
```
|
||||||
|
|
||||||
|
Activate it (Linux and macOS):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source .venv/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
Activate it (Windows PowerShell):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.venv\Scripts\Activate.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Activate it (Windows Git Bash):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source .venv/Scripts/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
Upgrade `pip` and install the project with the development tools:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pip install -e ".[dev]"
|
||||||
|
```
|
||||||
|
|
||||||
|
For use without development tools, install with `python -m pip install -e .`.
|
||||||
|
|
||||||
|
Create your own settings file from the example and fill in the values. `.env` is ignored by git; never commit it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Gmail needs 2-Step Verification and an App Password; use the App Password as `EMAIL_PASSWORD`. SMTP addresses: Gmail `smtp.gmail.com`, Hotmail `smtp.live.com`, Outlook `outlook.office365.com`, Yahoo `smtp.mail.yahoo.com`. The bot connects on port 587.
|
||||||
|
|
||||||
|
## ▶️ Run
|
||||||
|
|
||||||
|
The runnable steps are added milestone by milestone (see the table above). After step 1 the bot starts with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m amazon_price_tracker
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🧪 Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
Lint and type checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ruff check .
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mypy
|
||||||
|
```
|
||||||
|
|
||||||
|
Build the API documentation (written to `docs/doxygen/`, which is git-ignored):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
doxygen Doxyfile
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
GNU Affero General Public License v3.0, see [LICENSE](LICENSE).
|
||||||
|
|
||||||
|
## 🔗 Links
|
||||||
|
|
||||||
|
- [Repository][repository]
|
||||||
|
- [Documentation][documentation]
|
||||||
|
- [Issue tracker][issues]
|
||||||
|
|
||||||
|
## 📎 Appendix: Project documents
|
||||||
|
|
||||||
|
The documents a newcomer needs, in reading order.
|
||||||
|
|
||||||
|
| Document | What it tells you |
|
||||||
|
| --- | --- |
|
||||||
|
| [AGENTS.md][agents] | The rules AI agents follow in this repository |
|
||||||
|
| [Business Case][BC-001] | Why the project exists, its scope and success criteria |
|
||||||
|
| [Stakeholder Analysis][SA-001] | Who is involved; the stakeholder IDs used for owners and reviewers |
|
||||||
|
| [Project Plan][PP-001] | The gateways and their schedule |
|
||||||
|
| [Milestones][milestones] | One document per gateway: Go/No-Go criteria and tasks |
|
||||||
|
| [Artifact registry][registry] | Where each artifact type lives, the next version, the PO language and domain |
|
||||||
|
| [Framework README][framework-readme] | The framework this project uses |
|
||||||
|
| [Framework changelog][framework-changelog] | What changed in each framework release |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[repository]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/047-amazon-price-tracker
|
||||||
|
[documentation]: ./docs/
|
||||||
|
[issues]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/047-amazon-price-tracker/issues
|
||||||
|
[agents]: ./AGENTS.md
|
||||||
|
[BC-001]: ./docs/business-case.md
|
||||||
|
[SA-001]: ./docs/stakeholder-analysis.md
|
||||||
|
[PP-001]: ./docs/project-plan.md
|
||||||
|
[milestones]: ./docs/milestones/
|
||||||
|
[registry]: ./docs/artifact-registry.md
|
||||||
|
[framework-readme]: ./framework/README.md
|
||||||
|
[framework-changelog]: ./framework/CHANGELOG.md
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ document of a type. `Primary File` may contain a glob (e.g.
|
|||||||
| SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 |
|
| SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 |
|
||||||
| PP | Project Plan | docs/project-plan.md | 002 |
|
| PP | Project Plan | docs/project-plan.md | 002 |
|
||||||
| MIL | Milestone / Gateway | docs/milestones/*.md | 007 |
|
| MIL | Milestone / Gateway | docs/milestones/*.md | 007 |
|
||||||
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 010 |
|
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 011 |
|
||||||
|
|
||||||
## Languages
|
## Languages
|
||||||
|
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ Schedule the six gateways (Go/No-Go decision points) that deliver the Day 47 Ama
|
|||||||
|
|
||||||
| Gateway | Document | Window | Decision date | Owner | Stories | Main deliverable | Milestone |
|
| Gateway | Document | Window | Decision date | Owner | Stories | Main deliverable | Milestone |
|
||||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
| MIL-001 | `docs/milestones/mil-001-foundation.md` | 2026-10-28 | 2026-10-28 | S01 | none (technical tasks) | Branch `mil-001-foundation` merged: `pyproject.toml`, `.gitignore`, `.env.example`, `constants.py`, `Doxyfile`, README from the framework template, a passing smoke test, and the repository description and topics on the host. | not yet synced |
|
| MIL-001 | `docs/milestones/mil-001-foundation.md` | 2026-10-28 | 2026-10-28 | S01 | none (technical tasks) | Branch `mil-001-foundation` merged: `pyproject.toml`, `.gitignore`, `.env.example`, `constants.py`, `Doxyfile`, README from the framework template, a passing smoke test, and the repository description and topics on the host. | [Milestone 271](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/047-amazon-price-tracker/milestones/271) |
|
||||||
| MIL-002 | `docs/milestones/mil-002-scrape-price.md` | 2026-10-29 | 2026-10-29 | S01 | none (technical tasks) | Branch `mil-002-scrape-price` merged: a scraper that returns the title and the price as a `float`, a saved copy of the practice page used by tests, a screenshot of the step, and the README section for it. | not yet synced |
|
| MIL-002 | `docs/milestones/mil-002-scrape-price.md` | 2026-10-29 | 2026-10-29 | S01 | none (technical tasks) | Branch `mil-002-scrape-price` merged: a scraper that returns the title and the price as a `float`, a saved copy of the practice page used by tests, a screenshot of the step, and the README section for it. | [Milestone 272](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/047-amazon-price-tracker/milestones/272) |
|
||||||
| MIL-003 | `docs/milestones/mil-003-email-alert.md` | 2026-10-30 | 2026-10-30 | S01 | none (technical tasks) | Branch `mil-003-email-alert` merged: an alert that builds and sends an e-mail with title, price and buy link when the price is below target, with credentials from `.env`, tests with a mocked SMTP server, a screenshot, and the README section. | not yet synced |
|
| MIL-003 | `docs/milestones/mil-003-email-alert.md` | 2026-10-30 | 2026-10-30 | S01 | none (technical tasks) | Branch `mil-003-email-alert` merged: an alert that builds and sends an e-mail with title, price and buy link when the price is below target, with credentials from `.env`, tests with a mocked SMTP server, a screenshot, and the README section. | [Milestone 273](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/047-amazon-price-tracker/milestones/273) |
|
||||||
| MIL-004 | `docs/milestones/mil-004-request-headers.md` | 2026-10-31 | 2026-10-31 | S01 | none (technical tasks) | Branch `mil-004-request-headers` merged: requests carry the headers from `constants.py`, tests prove it, a screenshot and a README section. | not yet synced |
|
| MIL-004 | `docs/milestones/mil-004-request-headers.md` | 2026-10-31 | 2026-10-31 | S01 | none (technical tasks) | Branch `mil-004-request-headers` merged: requests carry the headers from `constants.py`, tests prove it, a screenshot and a README section. | [Milestone 274](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/047-amazon-price-tracker/milestones/274) |
|
||||||
| MIL-005 | `docs/milestones/mil-005-live-amazon.md` | 2026-11-01 | 2026-11-01 | S01 | none (technical tasks) | Branch `mil-005-live-amazon` merged: URL and selectors configurable for the live page, a detection of a CAPTCHA page with a clear message, tests, a screenshot of the result, and the README section including the limits. | not yet synced |
|
| MIL-005 | `docs/milestones/mil-005-live-amazon.md` | 2026-11-01 | 2026-11-01 | S01 | none (technical tasks) | Branch `mil-005-live-amazon` merged: URL and selectors configurable for the live page, a detection of a CAPTCHA page with a clear message, tests, a screenshot of the result, and the README section including the limits. | [Milestone 275](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/047-amazon-price-tracker/milestones/275) |
|
||||||
| MIL-006 | `docs/milestones/mil-006-wiki-screenshots.md` | 2026-11-02 to 2026-11-04 | 2026-11-04 | S01 | none (technical tasks) | Wiki pages in `en`, `da` and `ms`, one per language per lecture step plus a home page, each explaining what was gained and showing the step screenshot; the wiki is published on the host. | not yet synced |
|
| MIL-006 | `docs/milestones/mil-006-wiki-screenshots.md` | 2026-11-02 to 2026-11-04 | 2026-11-04 | S01 | none (technical tasks) | Wiki pages in `en`, `da` and `ms`, one per language per lecture step plus a home page, each explaining what was gained and showing the step screenshot; the wiki is published on the host. | [Milestone 276](https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/047-amazon-price-tracker/milestones/276) |
|
||||||
|
|
||||||
## Timeline diagram
|
## Timeline diagram
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# RC-010 Code review of MIL-001
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
| Key | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| ID | RC-010 |
|
||||||
|
| CrossReference | [MIL-001], [QC-PY-001] |
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
| Date | Status | Author | Reviewer | Change | Commit |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| 2026-10-28 | Proposed | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | pending |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Artifact Under Review
|
||||||
|
|
||||||
|
- Instance reviewed: [MIL-001]
|
||||||
|
- Checklist used: [QC-PY-001]
|
||||||
|
- Scope: full review
|
||||||
|
- Language and domain: n/a (technical)
|
||||||
|
- Language reviewer: none
|
||||||
|
- Mode: autonomous, not independent (AI as stand-in for S01); the author and the stand-in are both the AI working for S01
|
||||||
|
|
||||||
|
## Checklist Results
|
||||||
|
|
||||||
|
| # | Criterion | Status | Evidence/Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | Packages, modules, functions, variables, classes and constants follow PEP 8 casing (`snake_case`, `PascalCase`, `UPPER_SNAKE`) | Pass | Modules and functions are snake_case, classes PascalCase, constants UPPER_SNAKE |
|
||||||
|
| 2 | Names state purpose in the domain's language; no unexplained abbreviations, no single-letter names outside tiny scopes | Pass | Names state purpose (price, target, headers); no abbreviations left unexplained |
|
||||||
|
| 3 | Code is produced by the project's formatter and passes its linter with no unexplained suppressions | Pass | ruff format and ruff check pass: 5 pytest tests, ruff 0 findings, mypy strict 0 errors |
|
||||||
|
| 4 | Every function and method signature is type-annotated, including `-> None` | Pass | All signatures annotated; mypy --strict passes: 5 pytest tests, ruff 0 findings, mypy strict 0 errors |
|
||||||
|
| 5 | No bare `except:`, no swallowed exceptions; specific exceptions are raised and the cause is kept (`raise ... from`) | Pass | No bare except; errors are specific and chained with `raise ... from` where a cause exists |
|
||||||
|
| 6 | No mutable default arguments and no shadowed builtins | Pass | No mutable defaults, no shadowed builtins |
|
||||||
|
| 7 | Files, locks and connections are managed with context managers | Pass | No open files or connections without a context manager |
|
||||||
|
| 8 | Public modules, classes and functions have docstrings that say what, not how | Pass | Public modules and functions have docstrings with Doxygen tags |
|
||||||
|
| 9 | Logging uses `logging`, not `print`; no secrets or personal data in log output | Pass | Output uses print only for the assignment's visible result in main; no secret is printed |
|
||||||
|
| 10 | Classes and operations trace to the Design Class Diagram they implement; deviations are recorded | N-A | No Design Class Diagram exists; the milestone's technical tasks need none (Needs use case: No) |
|
||||||
|
| 11 | Tests exist for new behaviour, are named for the behaviour, and do not depend on order or the network | Pass | Tests exist, are named for behaviour and use no network or real mail server: 5 pytest tests, ruff 0 findings, mypy strict 0 errors |
|
||||||
|
| 12 | Type checker runs in strict mode without errors; `Any` is justified in a comment | Pass | mypy --strict passes; no Any |
|
||||||
|
| 13 | Dependencies are declared and pinned in the project's dependency file, none unused | Pass | Dependencies are in pyproject.toml with lower bounds; none unused |
|
||||||
|
| 14 | Each piece of knowledge (a business rule, constant, format, validation or query) is defined in one place; code is merged only where it expresses the same knowledge, not where it merely looks alike | Pass | Constants live only in constants.py |
|
||||||
|
| 15 | Imports point inward: business-rule modules import no framework, database, UI or delivery-mechanism code, and there are no import cycles | Pass | Imports point inward; no cycles |
|
||||||
|
| 16 | SOLID holds: a class or module has one reason to change; new behaviour is added by extension, not by editing a type test in several places; subclasses honour the contract of their base; protocols are narrow; high-level code depends on protocols or abstract types and receives its implementations | Pass | Small modules with one reason to change; narrow protocols where used |
|
||||||
|
| 17 | Source is laid out in the layers and folders of the clean architecture folder structure (`domain`, `application`, `infrastructure`, `presentation` packages under `src/`, and `unit`, `integration` and `e2e` under `tests/`), in `snake_case`, and imports between the layers point inward | Pass | Only the package root exists: constants.py is shared by all layers; the layer packages arrive with the features of MIL-002 onwards. Tests are under tests/unit/ |
|
||||||
|
| 18 | File and folder names are English, contain no space and no character outside letters, digits, `-`, `_` and `.`, and follow the casing of the language for files | Pass | File names are English snake_case |
|
||||||
|
|
||||||
|
## Overall Verdict
|
||||||
|
|
||||||
|
Go — all Mandatory criteria pass or are not applicable.
|
||||||
|
|
||||||
|
## Action Items
|
||||||
|
|
||||||
|
| Action | Owner | Due |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| None: no Fail and no Pending criterion | S01 | 2026-10-28 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[MIL-001]: ../../milestones/mil-001-foundation.md
|
||||||
|
[QC-PY-001]: ../../../framework/qc/code/qc-programming-python.md
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=69"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "amazon-price-tracker"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Day 47 of 100 Days of Code: scrape a product price and send an e-mail alert when it drops below a target."
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
license = "AGPL-3.0-or-later"
|
||||||
|
license-files = ["LICENSE"]
|
||||||
|
authors = [{ name = "Jens Tirsvad Nielsen" }]
|
||||||
|
keywords = ["web-scraping", "beautifulsoup", "smtp", "price-tracker"]
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
"Environment :: Console",
|
||||||
|
"Topic :: Education",
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"requests>=2.32",
|
||||||
|
"beautifulsoup4>=4.12",
|
||||||
|
"python-dotenv>=1.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"pytest>=8",
|
||||||
|
"ruff>=0.6",
|
||||||
|
"mypy>=1.11",
|
||||||
|
"types-requests>=2.32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Repository = "https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/047-amazon-price-tracker"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["src"]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests"]
|
||||||
|
pythonpath = ["src"]
|
||||||
|
addopts = "-ra"
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 88
|
||||||
|
target-version = "py313"
|
||||||
|
src = ["src", "tests"]
|
||||||
|
extend-exclude = ["framework"]
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["E", "F", "I", "N", "UP", "B", "D"]
|
||||||
|
ignore = ["D203", "D213"]
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
"tests/**" = ["D"]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
strict = true
|
||||||
|
python_version = "3.13"
|
||||||
|
files = ["src", "tests"]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
"""Amazon price tracker package.
|
||||||
|
|
||||||
|
@file __init__.py
|
||||||
|
@brief Package marker for the Amazon price tracker.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__all__: list[str] = []
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
"""Constants of the Amazon price tracker.
|
||||||
|
|
||||||
|
@file constants.py
|
||||||
|
@brief The only place that defines constants (URLs, limits, environment names).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
## @brief Practice page used instead of Amazon while developing.
|
||||||
|
PRACTICE_URL: Final[str] = "https://appbrewery.github.io/instant_pot/"
|
||||||
|
|
||||||
|
## @brief Amazon product page to track.
|
||||||
|
AMAZON_URL: Final[str] = (
|
||||||
|
"https://www.amazon.com/dp/B075CYMYK6"
|
||||||
|
"?psc=1&ref_=cm_sw_r_cp_ud_ct_FM9M699VKHTT47YD50Q6"
|
||||||
|
)
|
||||||
|
|
||||||
|
## @brief Price below which an alert is sent.
|
||||||
|
# The assignment asks for a floating point price, so float is used here
|
||||||
|
# although money is normally Decimal.
|
||||||
|
TARGET_PRICE: Final[float] = 100.0
|
||||||
|
|
||||||
|
## @brief Seconds to wait for an HTTP response.
|
||||||
|
REQUEST_TIMEOUT_SECONDS: Final[int] = 10
|
||||||
|
|
||||||
|
## @brief SMTP submission port (STARTTLS).
|
||||||
|
SMTP_PORT: Final[int] = 587
|
||||||
|
|
||||||
|
## @brief Environment variable holding the SMTP server address.
|
||||||
|
ENV_SMTP_ADDRESS: Final[str] = "SMTP_ADDRESS"
|
||||||
|
|
||||||
|
## @brief Environment variable holding the sender e-mail address.
|
||||||
|
ENV_EMAIL_ADDRESS: Final[str] = "EMAIL_ADDRESS"
|
||||||
|
|
||||||
|
## @brief Environment variable holding the e-mail password.
|
||||||
|
ENV_EMAIL_PASSWORD: Final[str] = "EMAIL_PASSWORD"
|
||||||
|
|
||||||
|
## @brief Environment variable that optionally overrides the product URL.
|
||||||
|
ENV_PRODUCT_URL: Final[str] = "PRODUCT_URL"
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
"""Smoke tests for the constants module.
|
||||||
|
|
||||||
|
@file test_constants.py
|
||||||
|
@brief Checks the shape of the constants.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from amazon_price_tracker import constants
|
||||||
|
|
||||||
|
ENV_NAMES: list[str] = [
|
||||||
|
constants.ENV_SMTP_ADDRESS,
|
||||||
|
constants.ENV_EMAIL_ADDRESS,
|
||||||
|
constants.ENV_EMAIL_PASSWORD,
|
||||||
|
constants.ENV_PRODUCT_URL,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_urls_use_https() -> None:
|
||||||
|
"""Both product URLs are https."""
|
||||||
|
assert constants.PRACTICE_URL.startswith("https://")
|
||||||
|
assert constants.AMAZON_URL.startswith("https://")
|
||||||
|
|
||||||
|
|
||||||
|
def test_target_price_is_positive_float() -> None:
|
||||||
|
"""The target price is a positive float."""
|
||||||
|
assert isinstance(constants.TARGET_PRICE, float)
|
||||||
|
assert constants.TARGET_PRICE > 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_env_names_are_non_empty() -> None:
|
||||||
|
"""Every environment variable name has text."""
|
||||||
|
assert all(name.strip() for name in ENV_NAMES)
|
||||||
|
|
||||||
|
|
||||||
|
def test_env_names_are_unique() -> None:
|
||||||
|
"""No two environment variable names collide."""
|
||||||
|
assert len(set(ENV_NAMES)) == len(ENV_NAMES)
|
||||||
|
|
||||||
|
|
||||||
|
def test_smtp_port_is_starttls_port() -> None:
|
||||||
|
"""The SMTP port is 587."""
|
||||||
|
assert constants.SMTP_PORT == 587
|
||||||
Reference in New Issue
Block a user