Skip to main content

CLI and Python API to fetch air quality index (AQI) data from the Montreal open data portal

Project description

AQI monitoring for the city of Montréal (Québec, Canada)

Latest Release Python CI Test Coverage PyPI Downloads License Code style: Ruff

A Python library and CLI tool to fetch, process, and expose air quality index (AQI) data from the City of Montréal open data platform.

We designed this project to provide:

  • scriptable output (JSON by default)
  • embeddable components as a Python library
  • automation-ready tools (Home Assistant, cron jobs, data pipelines)

Features

  • Fetches the latest air quality data from Montréal’s open data portal
  • Lists active air quality monitoring stations
  • Computes the AQI based on RSQA
  • Estimates pollutant concentrations from reported AQI contributions¹
  • Exposes structured station and pollutant data via Python models
  • Outputs machine-readable JSON from the CLI
  • Structured logging with optional debug mode
  • Test suite covering core logic, JSON contract, and CLI behavior

¹ Estimated concentrations come from rounded AQI values; treat them as approximations.

Performance & Optimization

  • Intelligent caching (TTL-based, max 100 entries)
  • Batch requests via get_stations_aqi() for parallel multi-station queries
  • Server-side filtering, sorting, and column selection (fields parameter)
  • Pagination support with offset parameter for large datasets
  • Debug logging includes full API URLs and request parameters

Requirements

  • Python 3.11 or newer
  • requests

Installation

From PyPI (recommended)

pip install montreal-aqi-api

From source

git clone https://github.com/normcyr/montreal-aqi-api.git
cd montreal-aqi-api
python3 -m venv venv
source venv/bin/activate
pip install .

CLI Usage

The CLI always outputs JSON on stdout and writes logs and diagnostics to stderr.

Fetch AQI for a specific station

montreal-aqi --station <station_id>

List available monitoring stations

montreal-aqi --list

Enable debug logging

montreal-aqi --station <station_id> --debug

Print the JSON in a pretty format

montreal-aqi --station <station_id> --pretty

or

montreal-aqi --list --pretty

No arguments

When you provide no arguments, the CLI returns a JSON error payload. We intentionally avoid interactive prompts to keep behavior predictable in automated environments.

Advanced examples

Fetch multiple stations

montreal-aqi --station 1,2,3 --pretty

Suppress output (for scripts)

montreal-aqi --station 80 --quiet
# No output if successful, useful for cron jobs

Verbose logging

montreal-aqi --station 80 --verbose
# Shows detailed logs including API request times and cache status

Combine options

montreal-aqi --list --pretty --verbose

Integrations

Use this library to integrate AQI monitoring with automated systems and workflows.

Home Assistant

Used by the custom Home Assistant integration:

Other Use Cases

  • Cron jobs
  • Data ingestion pipelines
  • Monitoring dashboards
  • Research / environmental analysis

JSON Contract — Version 1 (Frozen)

As of v0.4.0, we explicitly versioned and froze the JSON output contract. We formally specify the output format in: docs/json_contract_v1.md.

The official JSON Schema v1 governs the JSON output, and all payloads include:

{
  "version": 1,
  "type": "..."
}

Error Payload

{
  "version": 1,
  "type": "error",
  "error": {
    "code": "NO_DATA",
    "message": "No data available for this station"
  }
}

Stations List Payload

{
  "version": 1,
  "type": "stations",
  "stations": [
    {
      "station_id": "3",
      "name": "Saint-Jean-Baptiste",
      "borough": "Rivière-des-Prairies"
    }
  ]
}

Station AQI Payload

{
  "version": 1,
  "type": "station",
  "station_id": "80",
  "date": "2025-08-08",
  "hour": 10,
  "aqi": 49,
  "dominant_pollutant": "PM2.5",
  "pollutants": {
    "PM2.5": {
      "name": "PM2.5",
      "aqi": 49,
      "concentration": 34.3
    },
    "O3": {
      "name": "O3",
      "aqi": 22,
      "concentration": 70.4
    }
  }
}

Python Usage

Fetch AQI for a single station

from montreal_aqi_api.service import get_station_aqi

station = get_station_aqi("80")
if station:
    print(station.to_dict())

Fetch AQI for multiple stations (parallel requests)

For better performance when fetching data for multiple stations, use get_stations_aqi():

from montreal_aqi_api.service import get_stations_aqi

# Fetch AQI for multiple stations concurrently (default: 5 workers)
stations = get_stations_aqi(["1", "3", "5", "80"])

for station in stations:
    if station:
        print(f"Station {station.station_id}: AQI={station.aqi}")
    else:
        print("Failed to fetch data")

Domain objects (Station, Pollutant) expose explicit serialization helpers:

station = get_station_aqi("80")
data = station.to_dict()  # Returns fully serialized JSON-compatible dict
print(data["pollutants"]["PM2.5"])  # Access individual pollutants

Exit codes

The montreal-aqi CLI exits with explicit status codes to make it suitable for scripting, automation, and CI pipelines.

Exit code Meaning Description
0 Success Command executed successfully
1 Generic API error An unexpected internal API error occurred
2 API unreachable The CLI could not reach the Montreal Open Data API (network error, timeout, DNS, etc.)
3 Invalid API response API returned malformed or unexpected JSON payload

Details

  • The CLI reports all errors both via:
    • a structured JSON error payload on stdout
    • a non-zero process exit code
  • This ensures compatibility with:
    • shell scripts
    • cron jobs
    • CI/CD pipelines
    • Home Assistant / automation tools

Example

$ montreal-aqi --station 80
{
  "version": "1",
  "type": "error",
  "error": {
    "code": "API_UNREACHABLE",
    "message": "Montreal open data API is unreachable"
  }
}

$ echo $?
2

AQI Methodology

AQI values follow the methodology defined by the Réseau de surveillance de la qualité de l’air (RSQA).

Reference Values

Pollutant Full Name Reference
SO₂ Sulfur Dioxide 500 µg/m³
CO Carbon Monoxide 35 mg/m³
O₃ Ozone 160 µg/m³
NO₂ Nitrogen Dioxide 400 µg/m³
PM2.5 Particulate Matter 35 µg/m³

Project Status

  • JSON contract v1 frozen and validated by tests
  • Suitable for automation and integration
  • API stability guaranteed within v1

Contributing

Contributions are welcome.

Please ensure that:

  • tests pass (pytest)
  • the JSON contract remains backward compatible
  • tests cover any change affecting output
  • lint and format code with ruff (run ruff format --check . and ruff check .)

Open an issue before proposing breaking changes.


Data Source

Data retrieved from the Ville de Montréal Open Data Portal.


License

We license this project under the MIT License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

montreal_aqi_api-0.7.1.tar.gz (26.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

montreal_aqi_api-0.7.1-py3-none-any.whl (16.5 kB view details)

Uploaded Python 3

File details

Details for the file montreal_aqi_api-0.7.1.tar.gz.

File metadata

  • Download URL: montreal_aqi_api-0.7.1.tar.gz
  • Upload date:
  • Size: 26.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for montreal_aqi_api-0.7.1.tar.gz
Algorithm Hash digest
SHA256 9a5fd20c8c1efef9776380177c802701e935a53479ac354c6a0af82bd278f04d
MD5 8f48b8b26148a8aa95b3b9015e0908f1
BLAKE2b-256 41e33fd2b235dda61e12f94c699368b2de103b90d5f565b370cf154d3d659a7a

See more details on using hashes here.

File details

Details for the file montreal_aqi_api-0.7.1-py3-none-any.whl.

File metadata

File hashes

Hashes for montreal_aqi_api-0.7.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ec6a9bfa096477cc962078e8f0ab9764bec278fc967340e7d5a039db71aab5d1
MD5 66136006c51e01975c3a3b7c13c9e046
BLAKE2b-256 38f4d1fc7dac2c9435394026317622da1295db4689673dd93c39eafdc6505c90

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page