Skip to content
Open

v1.5 #11

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,42 @@

- name: Test with pytest
run: |
python -m pytest tests/ -v --tb=short
continue-on-error: true # Allow tests to fail until test suite is implemented
python -m pytest tests/ -v --tb=short --cov=vayuayan --cov-report=term-missing --cov-report=xml

- name: Test CLI functionality
run: |
python -m vayuayan --help
python -c "import vayuayan; print('Package imports successfully')"

docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install docs dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[docs]"

- name: Build docs (fail on broken build)
env:
SPHINX_BUILD: "true"
run: sphinx-build -b html docs docs/_build/html

security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

- name: Install bandit
run: |
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/docs-hostinger.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: docs-hostinger

on:
push:
branches: [main, master]
release:
types: [published]
workflow_dispatch:

permissions:
contents: read

jobs:
deploy:
runs-on: ubuntu-latest
concurrency:
group: docs-hostinger-${{ github.ref }}
cancel-in-progress: true

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .[docs]

- name: Build Sphinx site
env:
SPHINX_BUILD: "true"
run: sphinx-build -b html docs docs/_build/html

- name: Deploy to Hostinger
uses: burnett01/rsync-deployments@7.0.1
with:
switches: -avzr --delete
path: docs/_build/html/
remote_path: /var/www/r-pkgdown/${{ github.event.repository.name }}/docs/
remote_host: ${{ secrets.HOSTINGER_HOST }}
remote_port: ${{ secrets.HOSTINGER_PORT }}
remote_user: ${{ secrets.HOSTINGER_USER }}
remote_key: ${{ secrets.HOSTINGER_SSH_KEY }}
2 changes: 1 addition & 1 deletion .github/workflows/gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -e ".[docs]"

- name: Build docs
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ jobs:
name: python-package-distributions
path: dist/
- name: Sign the dists with Sigstore
uses: sigstore/gh-action-sigstore-python@v2.1.1
uses: sigstore/gh-action-sigstore-python@v3.0.0
with:
inputs: >-
./dist/*.tar.gz
Expand Down
Binary file added docs/_static/vayuayan_social_preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 14 additions & 18 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html

import importlib
import os
Expand All @@ -14,21 +11,16 @@
except Exception:
package_version = "0.1.0"

# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

project = "vayuayan"
copyright = "2025, Mahesh Prajapat and Saket Choudhary"
copyright = "2026, Mahesh Prajapat and Saket Choudhary"
author = "Mahesh Prajapat and Saket Choudhary"
release = package_version
version = os.environ.get("SMV_CURRENT_VERSION") or os.environ.get(
"READTHEDOCS_VERSION_NAME",
release,
)

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.viewcode",
Expand All @@ -37,14 +29,15 @@
"sphinx.ext.intersphinx",
"myst_nb",
"sphinx_multiversion",
"sphinxext.opengraph",
"sphinx_sitemap",
]

templates_path = ["_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]

language = "en"

# -- Autodoc configuration --------------------------------------------------
autodoc_default_options = {
"members": True,
"member-order": "bysource",
Expand All @@ -53,8 +46,6 @@
"exclude-members": "__weakref__",
}

# Mock heavy optional dependencies if they are unavailable so autodoc can
# import vayuayan modules without requiring compiled geospatial stacks.
_OPTIONAL_LIBS = [
"geopandas",
"geopy",
Expand All @@ -71,7 +62,6 @@
except Exception:
autodoc_mock_imports.append(_module)

# -- Napoleon settings -------------------------------------------------------
napoleon_google_docstring = True
napoleon_numpy_docstring = True
napoleon_include_init_with_doc = False
Expand All @@ -84,23 +74,30 @@
napoleon_use_param = True
napoleon_use_rtype = True

# -- Intersphinx mapping ----------------------------------------------------
intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
"pandas": ("https://pandas.pydata.org/docs/", None),
"requests": ("https://requests.readthedocs.io/en/latest/", None),
}

# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

html_theme = "furo"
html_static_path = ["_static"]
html_css_files = ["css/custom.css"]
html_baseurl = "https://vayuayan.saketlab.org/"

# Logo configuration
html_logo = "assets/vayuayan.png"

ogp_site_url = html_baseurl
ogp_site_name = "vayuayan: Air Quality & Pollution Data for Python"
ogp_image = html_baseurl + "_static/vayuayan_social_preview.png"
ogp_enable_meta_description = True
ogp_custom_meta_tags = [
'<meta name="twitter:card" content="summary_large_image">',
]
sitemap_url_scheme = "{link}"
html_extra_path = ["robots.txt"]

html_sidebars = {
"**": [
"sidebar/brand.html",
Expand All @@ -112,7 +109,6 @@
]
}

# -- Theme options -----------------------------------------------------------
html_theme_options = {
"navigation_with_keys": True,
"sidebar_hide_name": False, # Show name alongside logo
Expand Down
6 changes: 6 additions & 0 deletions docs/history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ History

Release history and changelog for vayuayan.

v0.1.5 (2026-06-27)
------------------

* Added Google Earth Engine fallback for PM2.5 data when AWS S3 returns 403, with automatic GEE project detection and ``setup_earth_engine()`` helper
* Added support for V5 (V5.GL.05.02, GWR-based) WUSTL ACAG data alongside the default V6

v0.1.4 (2025-11-30)
------------------

Expand Down
4 changes: 4 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.. meta::
:description: vayuayan is a Python package and CLI for fetching and analysing air quality data worldwide: WUSTL ACAG satellite PM2.5 (1998-2024) and CPCB India AQI monitoring (historical and live).
:keywords: air quality, PM2.5, pollution, AQI, CPCB, India, WUSTL ACAG, satellite, Python, air pollution data

vayuayan: Collect and Analyze Air Quality and Pollution Data
============================================================

Expand Down
4 changes: 2 additions & 2 deletions docs/notebooks/01_getting_started.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -455,9 +455,9 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python [conda env:mamba-alethia]",
"display_name": "Python 3",
"language": "python",
"name": "conda-env-mamba-alethia-py"
"name": "python3"
},
"language_info": {
"codemirror_mode": {
Expand Down
4 changes: 2 additions & 2 deletions docs/notebooks/02_historical_data_analysis.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,9 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python [conda env:base] *",
"display_name": "Python 3",
"language": "python",
"name": "conda-base-py"
"name": "python3"
},
"language_info": {
"codemirror_mode": {
Expand Down
4 changes: 4 additions & 0 deletions docs/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
User-agent: *
Allow: /

Sitemap: https://vayuayan.saketlab.org/sitemap.xml
15 changes: 11 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[build-system]
requires = ["setuptools>=45", "wheel"]
requires = ["setuptools>=77", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "vayuayan"
version = "0.1.4"
dynamic = ["version"]
description = "Package for fetching and analyzing air quality data from multiple sources"
readme = "README.md"
license = "MIT"
Expand Down Expand Up @@ -41,7 +41,9 @@ dependencies = [
"rioxarray>=0.15.0",
"xarray>=2024.7.0",
"netCDF4>=1.7.2",
"tqdm>=4.62.0"
"tqdm>=4.62.0",
"earthengine-api>=0.1.384",
"boto3>=1.26.0"
]

[project.optional-dependencies]
Expand All @@ -58,6 +60,8 @@ docs = [
"sphinx>=8.2.3",
"furo>=2024.8.6",
"sphinx-multiversion>=0.2.4",
"sphinxext-opengraph>=0.9.1",
"sphinx-sitemap>=2.6.0",
]
notebooks = [
"jupyter",
Expand All @@ -68,7 +72,7 @@ notebooks = [
[project.urls]
Homepage = "https://github.com/saketlab/vayuayan"
Repository = "https://github.com/saketlab/vayuayan.git"
Documentation = "https://saketlab.github.io/vayuayan"
Documentation = "https://vayuayan.saketlab.org"
"Bug Tracker" = "https://github.com/saketlab/vayuayan/issues"

[project.scripts]
Expand All @@ -77,6 +81,9 @@ vayuayan = "vayuayan.cli:main"
[tool.setuptools]
packages = ["vayuayan"]

[tool.setuptools.dynamic]
version = {attr = "vayuayan.__version__"}

[tool.setuptools.package-data]
vayuayan = ["py.typed"]

Expand Down
Binary file modified requirements.txt
Binary file not shown.
Empty file added tests/__init__.py
Empty file.
32 changes: 32 additions & 0 deletions tests/test_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Tests for config-path helpers and AQI threshold table."""

from pathlib import Path

from vayuayan.constants import (
AQI_CATEGORIES,
get_config_dir,
get_gee_project_file,
)


def test_get_config_dir(tmp_path, monkeypatch):
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path))
monkeypatch.setattr("platform.system", lambda: "Linux")
d = get_config_dir()
assert d == tmp_path / "vayuayan"
assert d.is_dir() # created on call


def test_get_gee_project_file(tmp_path, monkeypatch):
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path))
monkeypatch.setattr("platform.system", lambda: "Linux")
f = get_gee_project_file()
assert f == tmp_path / "vayuayan" / "gee_project"
assert isinstance(f, Path)


def test_aqi_categories_contiguous():
# Bands must be ordered and non-overlapping.
bands = list(AQI_CATEGORIES.values())
for lo, hi in zip(bands, bands[1:]):
assert lo["max"] + 1 == hi["min"]
26 changes: 26 additions & 0 deletions tests/test_exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""All custom exceptions must derive from CPCBError."""

import pytest

from vayuayan import exceptions as exc


@pytest.mark.parametrize(
"cls",
[
exc.NetworkError,
exc.DataParsingError,
exc.DataProcessingError,
exc.CityNotFoundError,
exc.StationNotFoundError,
exc.InvalidDataError,
exc.AuthenticationError,
exc.RateLimitError,
exc.ConfigurationError,
exc.FileNotFoundError,
],
)
def test_inherits_base(cls):
assert issubclass(cls, exc.CPCBError)
with pytest.raises(exc.CPCBError):
raise cls("boom")
Loading
Loading