A production-style weather data service with intelligent caching, query history, and dual interfaces (REST API + CLI). Built to demonstrate clean architecture, async Python, and real-world backend patterns.
Important
π§ PROJECT STATUS: Architecture and documentation completed. Development in progress β vertical slice (/weather/current endpoint) currently being implemented.
Note
OpenWeatherMap was initially considered as first weather provider, but Open-Meteo has been selected as the preferred provider to simplify the MVP, as it does not require an API key. See ADR-024.
This is a portfolio project designed to demonstrate:
- Clean Architecture (Ports & Adapters)
- Async Python across the entire stack
- Separation of concerns (API / domain / infrastructure)
- External API integration with caching, retry logic, and fallback strategies
- Professional engineering practices (ADRs, testing strategy, documentation)
git clone https://github.com/lridalc/weather-analytics-dashboard
cd weather-analytics-dashboard
# Install dependencies
make install-dev
# Configure environment
cp .env.example .env
# Run API
make dev
# Test endpoints
curl http://localhost:8000/health
curl "http://localhost:8000/weather/current?city=London"
# CLI usage
make cli now LondonThis project follows a vertical-slice, test-driven development approach, where each milestone delivers a fully working feature across all layers.
Note
For a more detailed insight, see development plan.
| Version | Focus | Status |
|---|---|---|
| 0.1.0 | Foundation (/health) |
β |
| 0.2.0 | Current Weather API (/weather/current) |
π§ |
| 0.3.0 | Forecast System (/weather/forecast) |
- |
| 0.4.0 | History Persistence (/weather/history) |
- |
| 0.5.0 | Retry & Resilience | - |
| 0.6.0 | Caching Layer | - |
| 1.0.0 | Production Release | - |
- Project scaffolding
- Documentation structure (README, ADRs, scope)
- Smoke tests (API + CLI)
- CI/CD pipeline setup and automation
- Settings & configuration system
- Base FastAPI application bootstrap
-
/healthendpoint implementation - Health check tests
- Minimal API wiring validation
- Domain models for weather
- Use case: get current weather
- Geocoding service (shared infrastructure)
- Open-Meteo adapter integration
-
/weather/currentendpoint with error mapping - CLI:
weather now <city> - Full integration tests
- Forecast domain model
- Forecast use case implementation
- Open-Meteo adapter extension for forecasts
-
/weather/forecastendpoint with error mapping - CLI:
weather forecast <city> --days N
- History domain contracts (port + models)
- SQLite repository with aiosqlite
- FIFO eviction (max 10 entries per city)
- History retrieval service
- Automatic query persistence on current
-
/weather/historyendpoint with error mapping - CLI:
weather history <city>
- HTTP client with exponential backoff (tenacity)
- Global rate limiting (quota protection)
- Migration of OpenMeteoAtapter to retry-enabled client
- Retry only on transient errors (5xx, timeouts)
- Weather cache decorator (5 min TTL)
- FIFO eviction for weather cache
- Geocoding cache (7 days TTL, internal to service)
- Cache hit/miss integration tests
- Dependency wiring for cached provider
- All endpoints complete
- CLI feature parity with API
- Full async execution (no blocking I/O)
- Clean Architecture enforced across layers
- High test coverage achieved
- Production-ready documentation
| Endpoint | Description |
|---|---|
/weather/current |
Current temperature |
/weather/forecast |
Multi-day forecast |
/weather/history |
Last 10 queries |
/health |
Service health |
β Automatic OpenAPI docs at /docs
weather now Madrid
weather forecast Paris --days 5
weather history TokyoSame business logic as API β different interface.
Two-layer cache system:
| Layer | TTL | Purpose |
|---|---|---|
| Geocoding | 7 days | Location resolution |
| Weather | 5 minutes | Weather data |
β Reduces API calls significantly β Improves response time (<200ms cached) β FIFO eviction prevents memory leaks
Architectural note: These two caches operate at different layers:
- Weather cache wraps the domain port (system-wide)
- Geocoding cache is internal to the shared geocoding service (infrastructure detail)
From a user perspective, both contribute to performance optimization.
- Automatic retry with exponential backoff on transient external API failures
- Global rate limiting (55 req/min) to protect the external API quota
- Graceful degradation when the provider is unavailable
- SQLite persistence
- Last 10 queries per location
- FIFO eviction
- Survives restarts
- FastAPI (ASGI)
- HTTPX (non-blocking HTTP)
- SQLAlchemy async + aiosqlite
β No blocking I/O β Efficient concurrent handling
This project follows Clean Architecture + Ports & Adapters:
Presentation (API / CLI)
β
Application (Use Cases)
β
Domain (Business Logic)
β
Infrastructure (DB, Cache, External APIs)
- Provider Pattern (Port + Adapter) β external weather services abstraction
- Repository Pattern β database abstraction
- Decorator Pattern β caching layer
- Service Layer β use case orchestration
- Dependency Injection β testability
π Documentation:
Weather data retrieval is provider-driven:
- The domain interacts with a single abstraction:
WeatherProvider - Each provider implementation decides how to resolve a location
- Resolve location β coordinates (via shared geocoding service)
- Fetch weather data using coordinates
This logic is fully encapsulated inside the provider adapter, keeping the domain and application layers independent of provider-specific requirements.
β No geocoding concepts leak into the domain β Shared geocoding service prevents logic duplication across providers β Easy to swap providers without changing business logic
The current structure is the foundational layer. The system will evolve incrementally following the design defined in ADR-023.
.
βββ docs/ # Architecture, ADRs, and development planning
β βββ architecture.md
β βββ decisions.md
β βββ development-plan.md
β βββ project-scope.md
β
βββ src/weather_analytics_dashboard/
β βββ application/ # Use cases (business workflows)
β β βββ services/
β β βββ get_current_weather.py
β βββ config/ # Configuration, settings, constants, and exceptions
β β βββ constants.py
β β βββ exceptions.py
β β βββ logging.py
β β βββ settings.py
β βββ domain/ # Core business logic and models
β β βββ exceptions.py
β β βββ models.py
β β βββ ports.py
β βββ infrastructure/ # External systems (DB, APIs, cache)
β βββ presentation/ # Interfaces (API + CLI)
β β βββ api/
β β β βββ app.py
β β β βββ dependencies.py
β β β βββ routes/
β β β β βββ health.py
β β β βββ schemas/
β β β βββ health.py
β β βββ cli/
β β βββ main.py
β βββ bootstrap.py # Dependency injection and app initialization
β βββ main.py # Application entry point
β
βββ tests/
β βββ integration/ # Integration tests (API, DB)
β β βββ bootstrap/
β β βββ config/
β β βββ presentation/
β β β βββ api/
β β β βββ cli/
β β βββ test_00_smoke.py
β βββ unit/ # Unit tests (domain & services)
β β βββ config/
β β βββ test_settings.py
β βββ conftest.py # Pytest fixtures and configuration
β
βββ .github/ # CI/CD workflows, actions, and PR templates
β βββ actions/
β βββ workflows/
β βββ pull_request_template.md
β
βββ Makefile # Development commands
βββ pyproject.toml # Project configuration and dependencies
βββ README.mdGET /weather/current?city=London{
"city": "London",
"temperature": 18.5,
"timestamp": "2026-04-08T12:00:00Z"
}GET /weather/forecast?city=London&days=3{
"city": "London",
"forecast": [
{
"date": "2026-04-09",
"min_temp": 12.1,
"max_temp": 19.3,
"avg_temp": 15.7
}
]
}GET /weather/history?city=London{
"city": "London",
"history": [
{
"timestamp": "2026-04-08T12:00:00Z",
"temperature": 18.5
}
]
}{
"error": {
"code": "CITY_NOT_FOUND",
"message": "Location could not be resolved"
}
}| Type | Scope |
|---|---|
| Unit | Domain + services |
| Integration | API + DB |
| Mocks | External providers |
make test| Variable | Required | Default |
|---|---|---|
RATE_LIMIT_RPM |
β | 250 |
CACHE_TTL_WEATHER |
β | 300 |
CACHE_TTL_GEOCODING |
β | 604800 |
CACHE_MAX_SIZE |
β | 100 |
REQUEST_TIMEOUT_SECONDS |
β | 10 |
RETRY_MAX_ATTEMPTS |
β | 3 |
RETRY_INITIAL_WAIT_SECONDS |
β | 1 |
ENVIRONMENT |
β | DEV |
LOG_LEVEL |
β | INFO |
| Metric | Expected |
|---|---|
| Cached response | <200ms |
| Cache hit rate | ~60β90% |
| Memory usage | <50MB |
This project uses a Makefile to standardize development workflows and ensure consistency across environments.
Tip
Running make without arguments shows all availble commands (equivalent to make help).
π‘ All commands use uv run internally, so you donβt need to manually activate a virtual environment.
make all # Install everything necessary for development (install-dev + pre-commit)
make install # Install production dependencies
make install-dev # Install all dependencies including development toolsmake dev # Run API with hot reload (development)
make run # Run API without reload (production-like)
make cli # Run CLI toolmake test # Run all tests
make test-smoke # Run smoke tests
make test-bootstrap # Run bootstrap tests
make test-unit # Run unit tests
make test-integration # Run integration tests
make test-cov # Run tests with coverage reportAll tests are run with verbose output.
make lint # Run ruff linter
make format # Auto-format code
make format-diff # Show formatting differences without applying changes
make format-check # Check formatting without modifying files
make type-check # Run mypy static type checkingmake pre-commit # Install pre-commit hooks
make pre-commit-force # Force reinstall pre-commit hooks
make pre-commit-all # Run all hooks on all files
make pre-commit-run HOOK=ruff # Run specific hook
make pre-commit-update # Update hooks to latest versions
make pre-commit-uninstall # Uninstall pre-commit hooksmake checkRuns all quality checks:
- Linting (ruff)
- Formatting validation (ruff)
- Tests (pytest)
- Type checking (mypy)
make cleanRemoves cache files and temporary artifacts:
__pycache__.pyc.mypy_cache.pytest_cache.ruff_cache
make version # Show current version
make release # Show release process steps
make release VERSION=0.2.0 # Execute release (creates tag, pushes, syncs branches)make ci # Run complete CI pipeline locally (same as GitHub Actions)make helpDisplays all available commands (default).
gunicorn weather_analytics_dashboard.main:app \
--workers 4 \
--worker-class uvicorn.workers.UvicornWorker- Redis cache
- Docker support
- Cloud deployment (Railway / Render)
- Metrics endpoint (Prometheus)
- Redis cache
- PostgreSQL support
- Docker
lridalc - https://github.com/lridalc
Project Link - https://github.com/lridalc/weather-analytics-dashboard
This project is intentionally designed to reflect real-world backend engineering, including:
- Clear architectural boundaries
- Explicit trade-offs (documented via ADRs)
- Provider abstraction without leaking infrastructure details
- Resilience patterns (retry with exponential backoff)
- Async-first design
- Testable, modular components
This project was built to practice designing a production-ready backend system from scratch, focusing on maintainability, scalability, and real-world trade-offs.
It is not just a weather API wrapperβit is a system design exercise implemented in code.