Price Compare is a Python CLI application for comparing product prices across multiple stores with a modular architecture designed for future API-backed and scraping-backed integrations.
The current implementation supports Walmart, Kroger, Home Depot, and Menards through store-specific adapters. Kroger and Walmart can now run in optional live API modes when credentials are configured; Home Depot and Menards currently use local structured catalogs. Results are normalized into a shared schema, matched across stores, stored in SQLite for price history, and surfaced through a CLI that supports exports, watchlists, history lookups, and scheduled reruns.
- Modular adapter architecture with one adapter per store
- Shared normalized product schema using Pydantic models
- Matching and comparison engine with cheapest-item detection
- SQLite-backed persistence for searches, products, snapshots, comparison runs, and watchlists
- CLI support for search, watchlist, history, and scheduled run modes
- JSON and CSV export options
- Configurable timeout, retries, rate limiting, enabled stores, database path, default ZIP code, log level, user agent, and live API settings for Kroger and Walmart
- Unit and mocked adapter tests with no live HTTP calls
- Python 3.14
- pip
pip install -e .[dev]Copy config/settings.toml.example to config/settings.toml and adjust values as needed.
Example settings:
request_timeout = 10.0
retry_count = 3
rate_limit_delay = 0.5
log_level = "INFO"
user_agent = "price-compare/0.1.0"
enabled_stores = ["walmart", "kroger", "home_depot", "menards"]
database_path = "data/price_compare_storage.sqlite3"
default_zip_code = "60601"
use_live_kroger_api = false
kroger_api_base_url = "https://api.kroger.com/v1"
kroger_client_id = ""
kroger_client_secret = ""
kroger_scope = "product.compact"
kroger_search_limit = 10
use_live_walmart_api = false
walmart_api_base_url = "https://marketplace.walmartapis.com/v3"
walmart_client_id = ""
walmart_client_secret = ""
walmart_search_limit = 10Environment variable overrides are also supported for the live Kroger and Walmart integrations:
USE_LIVE_KROGER_API=true
KROGER_CLIENT_ID=your-client-id
KROGER_CLIENT_SECRET=your-client-secret
KROGER_SCOPE=product.compact
USE_LIVE_WALMART_API=true
WALMART_CLIENT_ID=your-client-id
WALMART_CLIENT_SECRET=your-client-secret
WALMART_API_BASE_URL=https://marketplace.walmartapis.com/v3
PRICE_COMPARE_USER_AGENT=price-compare/0.1.0
To use live Kroger product data instead of the local sample catalog, create a Kroger developer application and configure credentials in config/settings.toml or environment variables.
Suggested settings:
use_live_kroger_api = true
kroger_api_base_url = "https://api.kroger.com/v1"
kroger_client_id = "your-client-id"
kroger_client_secret = "your-client-secret"
kroger_scope = "product.compact"Once enabled, Kroger searches use the live API and pass the ZIP code through as a location hint. If live mode is enabled without credentials, the adapter logs a warning and falls back to the local sample catalog.
To use live Walmart product data instead of the local sample catalog, configure your Walmart Marketplace credentials in config/settings.toml or environment variables.
Suggested settings:
use_live_walmart_api = true
walmart_api_base_url = "https://marketplace.walmartapis.com/v3"
walmart_client_id = "your-client-id"
walmart_client_secret = "your-client-secret"
walmart_search_limit = 10Once enabled, Walmart searches use the live marketplace catalog search API. If live mode is enabled without credentials, the adapter logs a warning and falls back to the local sample catalog.
Run the CLI from the project root:
python main.pyAvailable CLI flows:
search: compare products across enabled storeswatchlist: list saved watchlist queries and optionally rerun themhistory: show historical prices for a product nameschedule: manually trigger a scheduled-style rerun of saved watchlist entrieskroger-check: verify live Kroger API credentials and a ZIP-based searchwalmart-check: verify live Walmart API credentials and a query-based search
When you start the app, it prompts for an action:
Choose action [search/watchlist/history/schedule/kroger-check/walmart-check] (default search):
Use search to compare current results across enabled stores.
Typical prompts:
Enter product search query:
Enter ZIP code [60601]:
Export results? [none/json/csv/both]:
Save this query to watchlist? [y/N]:
Optional alert threshold price (press Enter to skip):
Example:
Choose action [search/watchlist/history/schedule/kroger-check/walmart-check] (default search): search
Enter product search query: paper towels
Enter ZIP code [60601]: 60601
Export results? [none/json/csv/both]: json
Save this query to watchlist? [y/N]: y
Optional alert threshold price (press Enter to skip): 9.50
What happens:
- The app runs enabled store adapters.
- Kroger uses the live API when
use_live_kroger_api = trueand credentials are configured. - Walmart uses the live API when
use_live_walmart_api = trueand credentials are configured. - Products are normalized and matched across stores.
- The CLI prints grouped comparison results and highlights the cheapest item.
- If you export, files are written to
exports/. - If you save the query, it is stored in SQLite for later watchlist and alert checks.
Full example session:
$ python main.py
Choose action [search/watchlist/history/schedule/kroger-check/walmart-check] (default search): search
Enter product search query: paper towels
Enter ZIP code [60601]: 60601
Price comparison results
------------------------
Query: paper towels
ZIP code: 60601
Stores enabled: walmart, kroger, home_depot, menards
Group 1: group-1
Confidence: exact
Price difference: $1.50
store | product name | price | unit price | confidence | cheapest
--------+-----------------------------------+--------+------------+------------+---------
walmart | Bounty Select-A-Size Paper Towels | $12.49 | $1.56 | exact |
kroger | Bounty Select-A-Size Paper Towels | $10.99 | $1.37 | exact | YES
menards | Bounty Select-A-Size Paper Towels | $10.99 | $1.37 | exact |
Cheapest item: kroger | Bounty Select-A-Size Paper Towels | $10.99
Lowest saved price: kroger | Bounty Select-A-Size Paper Towels | $10.99
Last known prices by store:
- walmart: $12.49
- kroger: $10.99
- menards: $10.99
Price changes since last run:
- walmart: $12.49 -> $12.49 (change $0.00)
- kroger: $10.99 -> $10.99 (change $0.00)
- menards: $10.99 -> $10.99 (change $0.00)
Export results? [none/json/csv/both]: json
Exported JSON: exports/paper_towels_60601.json
Save this query to watchlist? [y/N]: y
Optional alert threshold price (press Enter to skip): 9.50
Saved to watchlist.
In that example, the person:
- chooses
searchat the first prompt - enters a product query and ZIP code
- reviews the comparison output
- exports the results as JSON
- saves the query to the watchlist
- sets an alert threshold for later checks
Use watchlist to review saved queries and optionally rerun them immediately.
Example:
Choose action [search/watchlist/history/schedule/kroger-check/walmart-check] (default search): watchlist
Re-run all watchlist queries now? [y/N]: y
Example interaction:
$ python main.py
Choose action [search/watchlist/history/schedule/kroger-check/walmart-check] (default search): watchlist
Watchlist
---------
1. paper towels (60601)
2. drill (60601)
Re-run all watchlist queries now? [y/N]: y
This mode is useful when you want to check tracked items without retyping each search.
Use history to inspect saved pricing data for a product name that has already been captured in earlier runs.
Example:
Choose action [search/watchlist/history/schedule/kroger-check/walmart-check] (default search): history
Enter product name for history: Bounty Select-A-Size Paper Towels
Example interaction:
$ python main.py
Choose action [search/watchlist/history/schedule/kroger-check/walmart-check] (default search): history
Enter product name for history: Bounty Select-A-Size Paper Towels
Price history
-------------
Historical lowest: kroger | Bounty Select-A-Size Paper Towels | $10.99
Last known prices by store:
- walmart: $12.49
- kroger: $10.99
- menards: $10.99
Recorded price history:
- 2026-04-18T01:59:40.245752+00:00 | walmart | $12.49 | Bounty Select-A-Size Paper Towels
- 2026-04-18T01:59:40.246676+00:00 | kroger | $10.99 | Bounty Select-A-Size Paper Towels
- 2026-04-18T01:59:40.248020+00:00 | menards | $10.99 | Bounty Select-A-Size Paper Towels
This mode shows:
- historical lowest saved price
- last known price by store
- recorded price history entries over time
Use schedule to manually trigger a batch-style rerun of all saved watchlist entries.
Example:
Choose action [search/watchlist/history/schedule/kroger-check/walmart-check] (default search): schedule
Example interaction:
$ python main.py
Choose action [search/watchlist/history/schedule/kroger-check/walmart-check] (default search): schedule
Scheduled run starting...
This mode behaves like a simple scheduled execution pass and prints fresh comparison output for each saved watchlist query.
Use kroger-check to verify that live Kroger API mode is configured correctly before relying on it in the full comparison flow.
Example interaction:
$ python main.py
Choose action [search/watchlist/history/schedule/kroger-check/walmart-check] (default search): kroger-check
Kroger live connectivity check
-----------------------------
Config summary:
- live API enabled: yes
- API base URL: https://api.kroger.com/v1
- scope: product.compact
- search limit: 10
- client ID configured: yes
- client secret configured: yes
- default ZIP code: 60601
Enter Kroger test query [milk]: milk
Enter ZIP code [60601]: 62959
Connection successful. Retrieved 3 raw result(s).
Sample normalized results:
store | product name | price | unit price | confidence | cheapest
-------+---------------------------------+-------+------------+------------+---------
kroger | Simple Truth Organic Whole Milk | $3.99 | $0.07 | n/a | YES
If live Kroger mode is disabled or credentials are missing, the CLI explains that directly instead of running the check. It also prints a safe config summary so you can quickly see whether the live mode flag, base URL, scope, and credential presence are set correctly without exposing the secret value.
Use walmart-check to verify that live Walmart API mode is configured correctly before relying on it in the full comparison flow.
Example interaction:
$ python main.py
Choose action [search/watchlist/history/schedule/kroger-check/walmart-check] (default search): walmart-check
Walmart live connectivity check
------------------------------
Config summary:
- live API enabled: yes
- API base URL: https://marketplace.walmartapis.com/v3
- search limit: 10
- client ID configured: yes
- client secret configured: yes
- default ZIP code: 60601
Enter Walmart test query [milk]: milk
Enter ZIP code [60601]: 62959
Connection successful. Retrieved 2 raw result(s).
Sample normalized results:
store | product name | price | unit price | confidence | cheapest
--------+------------------------+-------+------------+------------+---------
walmart | Great Value Whole Milk | $3.78 | n/a | n/a | YES
If live Walmart mode is disabled or credentials are missing, the CLI explains that directly and shows a safe config summary without exposing the secret value.
- Price history is built from saved comparison runs in the local SQLite database.
- History lookups work best when you search using consistent product terms over time.
- Matching uses identifiers, brand and size checks, and fuzzy title similarity to avoid bad comparisons.
PyCharm entry point:
- Script path: main.py
- Working directory:
D:\Dev\Python_Projects\Price_Compare
Run the full test suite:
python -m pytestThe suite covers:
- normalization and model validation
- matching and comparison logic
- repository and history behavior
- adapter parsing with mocked/local inputs only
- HTTP and parser failure handling
- logging configuration and adapter debug traces
Project layout:
- app/cli.py: CLI entry flow and user-facing output
- app/core/: configuration, logging, and shared exceptions
- app/models/: shared domain models and comparison result structures
- app/adapters/: one adapter per store plus registry and local catalog helpers
- app/services/: matching and comparison orchestration
- app/storage/: SQLite initialization and repository access
- app/utils/: HTTP, parsing, retry, and matching utilities
- tests/: unit and adapter test suite
Comparison flow:
- The CLI gathers the search query and location.
- The adapter registry runs enabled store adapters and normalizes results.
- The matching service groups likely-equivalent products across stores.
- The comparison service identifies the cheapest option, computes price changes, and checks alerts.
- The repository persists results for later history and watchlist use.
- Kroger and Walmart support optional live API paths; Home Depot and Menards are still local-data adapters for now.
- The adapter and HTTP layers are intentionally separated so live APIs or scrapers can replace the local data sources later without rewriting the comparison logic.