aqi-in-api 0.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aqi_in_api-0.0.2/.github/CODEOWNERS +1 -0
- aqi_in_api-0.0.2/.github/FUNDING.yml +15 -0
- aqi_in_api-0.0.2/.github/ISSUE_TEMPLATE/1-question.md +11 -0
- aqi_in_api-0.0.2/.github/ISSUE_TEMPLATE/2-bug-report.md +19 -0
- aqi_in_api-0.0.2/.github/ISSUE_TEMPLATE/3-feature-request.md +33 -0
- aqi_in_api-0.0.2/.github/PULL_REQUEST_TEMPLATE +5 -0
- aqi_in_api-0.0.2/.github/dependabot.yml +11 -0
- aqi_in_api-0.0.2/.github/workflows/publish.yml +97 -0
- aqi_in_api-0.0.2/.github/workflows/pull_request.yml +66 -0
- aqi_in_api-0.0.2/.gitignore +30 -0
- aqi_in_api-0.0.2/PKG-INFO +119 -0
- aqi_in_api-0.0.2/README.md +106 -0
- aqi_in_api-0.0.2/example.py +38 -0
- aqi_in_api-0.0.2/pyproject.toml +62 -0
- aqi_in_api-0.0.2/src/aqi_in_api/__init__.py +9 -0
- aqi_in_api-0.0.2/src/aqi_in_api/_client.py +238 -0
- aqi_in_api-0.0.2/src/aqi_in_api/_constants.py +19 -0
- aqi_in_api-0.0.2/src/aqi_in_api/_exceptions.py +12 -0
- aqi_in_api-0.0.2/src/aqi_in_api/_token.py +17 -0
- aqi_in_api-0.0.2/src/aqi_in_api/_utils.py +34 -0
- aqi_in_api-0.0.2/src/aqi_in_api/models.py +229 -0
- aqi_in_api-0.0.2/tests/test_client.py +104 -0
- aqi_in_api-0.0.2/tests/test_exceptions.py +17 -0
- aqi_in_api-0.0.2/tests/test_token.py +26 -0
- aqi_in_api-0.0.2/tests/test_utils.py +36 -0
- aqi_in_api-0.0.2/uv.lock +232 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@GuyKh
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# These are supported funding model platforms
|
|
2
|
+
|
|
3
|
+
github: guykh # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
|
4
|
+
patreon: # Replace with a single Patreon username
|
|
5
|
+
open_collective: # Replace with a single Open Collective username
|
|
6
|
+
ko_fi: # Replace with a single Ko-fi username
|
|
7
|
+
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
8
|
+
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
9
|
+
liberapay: # Replace with a single Liberapay username
|
|
10
|
+
issuehunt: # Replace with a single IssueHunt username
|
|
11
|
+
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
|
12
|
+
polar: # Replace with a single Polar username
|
|
13
|
+
buy_me_a_coffee: guykh # Replace with a single Buy Me a Coffee username
|
|
14
|
+
thanks_dev: # Replace with a single thanks.dev username
|
|
15
|
+
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Question
|
|
3
|
+
about: Questions about this project
|
|
4
|
+
labels: question
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
### Checklist
|
|
8
|
+
- [ ] There are no similar issues or pull requests about this question.
|
|
9
|
+
|
|
10
|
+
### Describe your question
|
|
11
|
+
<!-- A clear and concise description of what the question is. -->
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Report a bug to help improve this project
|
|
4
|
+
labels: bug
|
|
5
|
+
---
|
|
6
|
+
### Describe the bug
|
|
7
|
+
<!-- A clear and concise description of what the bug is. -->
|
|
8
|
+
|
|
9
|
+
### To reproduce
|
|
10
|
+
<!-- Provide a *minimal* example with steps to reproduce the bug locally.-->
|
|
11
|
+
|
|
12
|
+
### Expected behavior
|
|
13
|
+
<!-- A clear and concise description of what you expected to happen. -->
|
|
14
|
+
|
|
15
|
+
### Actual behavior
|
|
16
|
+
<!-- A clear and concise description of what actually happens. -->
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Additional context
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Suggest an idea for this project.
|
|
4
|
+
labels: feature, enhancement
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
### Checklist
|
|
8
|
+
|
|
9
|
+
<!-- Please make sure you check all these items before submitting your feature request. -->
|
|
10
|
+
|
|
11
|
+
- [ ] There are no similar issues or pull requests for this yet.
|
|
12
|
+
|
|
13
|
+
### Is your feature related to a problem? Please describe.
|
|
14
|
+
|
|
15
|
+
<!-- A clear and concise description of what you are trying to achieve.
|
|
16
|
+
|
|
17
|
+
Eg "I want to be able to [...] but I can't because [...]". -->
|
|
18
|
+
|
|
19
|
+
## Describe the solution you would like.
|
|
20
|
+
|
|
21
|
+
<!-- A clear and concise description of what you would want to happen.
|
|
22
|
+
|
|
23
|
+
For API changes, try to provide a code snippet of what you would like the API to look like.
|
|
24
|
+
-->
|
|
25
|
+
|
|
26
|
+
## Describe alternatives you considered
|
|
27
|
+
|
|
28
|
+
<!-- Please describe any alternative solutions or features you've considered to solve
|
|
29
|
+
your problem and why they wouldn't solve it. -->
|
|
30
|
+
|
|
31
|
+
## Additional context
|
|
32
|
+
|
|
33
|
+
<!-- Provide any additional context, screenshots, tracebacks, etc. about the feature here. -->
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# To get started with Dependabot version updates, you'll need to specify which
|
|
2
|
+
# package ecosystems to update and where the package manifests are located.
|
|
3
|
+
# Please see the documentation for all configuration options:
|
|
4
|
+
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
|
5
|
+
|
|
6
|
+
version: 2
|
|
7
|
+
updates:
|
|
8
|
+
- package-ecosystem: "pip"
|
|
9
|
+
directory: "/"
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "weekly"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "[0-9]+.[0-9]+.[0-9]+"
|
|
7
|
+
- "[0-9]+.[0-9]+.[0-9]+a[0-9]+"
|
|
8
|
+
- "[0-9]+.[0-9]+.[0-9]+b[0-9]+"
|
|
9
|
+
- "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
|
|
10
|
+
|
|
11
|
+
env:
|
|
12
|
+
PACKAGE_NAME: "py-aqi-in-api"
|
|
13
|
+
OWNER: "GuyKh"
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
details:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
outputs:
|
|
19
|
+
new_version: ${{ steps.release.outputs.new_version }}
|
|
20
|
+
suffix: ${{ steps.release.outputs.suffix }}
|
|
21
|
+
tag_name: ${{ steps.release.outputs.tag_name }}
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v7
|
|
24
|
+
|
|
25
|
+
- name: Extract tag and Details
|
|
26
|
+
id: release
|
|
27
|
+
run: |
|
|
28
|
+
if [ "${{ github.ref_type }}" = "tag" ]; then
|
|
29
|
+
TAG_NAME=${GITHUB_REF#refs/tags/}
|
|
30
|
+
NEW_VERSION=$(echo $TAG_NAME | awk -F'-' '{print $1}')
|
|
31
|
+
SUFFIX=$(echo $TAG_NAME | grep -oP '[a-z]+[0-9]+' || echo "")
|
|
32
|
+
echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
|
|
33
|
+
echo "suffix=$SUFFIX" >> "$GITHUB_OUTPUT"
|
|
34
|
+
echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT"
|
|
35
|
+
echo "Version is $NEW_VERSION"
|
|
36
|
+
echo "Suffix is $SUFFIX"
|
|
37
|
+
echo "Tag name is $TAG_NAME"
|
|
38
|
+
else
|
|
39
|
+
echo "No tag found"
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
check_pypi:
|
|
44
|
+
needs: details
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
steps:
|
|
47
|
+
- name: Fetch information from PyPI
|
|
48
|
+
run: |
|
|
49
|
+
response=$(curl -s https://pypi.org/pypi/${{ env.PACKAGE_NAME }}/json || echo "{}")
|
|
50
|
+
latest_previous_version=$(echo $response | jq --raw-output "select(.releases != null) | .releases | keys_unsorted | last")
|
|
51
|
+
if [ -z "$latest_previous_version" ]; then
|
|
52
|
+
echo "Package not found on PyPI."
|
|
53
|
+
latest_previous_version="0.0.0"
|
|
54
|
+
fi
|
|
55
|
+
echo "Latest version on PyPI: $latest_previous_version"
|
|
56
|
+
echo "latest_previous_version=$latest_previous_version" >> $GITHUB_ENV
|
|
57
|
+
|
|
58
|
+
- name: Compare versions and exit if not newer
|
|
59
|
+
run: |
|
|
60
|
+
NEW_VERSION=${{ needs.details.outputs.new_version }}
|
|
61
|
+
LATEST_VERSION=$latest_previous_version
|
|
62
|
+
if [ "$(printf '%s\n' "$LATEST_VERSION" "$NEW_VERSION" | sort -rV | head -n 1)" != "$NEW_VERSION" ] || [ "$NEW_VERSION" == "$LATEST_VERSION" ]; then
|
|
63
|
+
echo "The new version $NEW_VERSION is not greater than the latest version $LATEST_VERSION on PyPI."
|
|
64
|
+
exit 1
|
|
65
|
+
else
|
|
66
|
+
echo "The new version $NEW_VERSION is greater than the latest version $LATEST_VERSION on PyPI."
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
setup_and_build:
|
|
70
|
+
needs: [details, check_pypi]
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
steps:
|
|
73
|
+
- uses: actions/checkout@v7
|
|
74
|
+
|
|
75
|
+
- name: Set up Python
|
|
76
|
+
uses: actions/setup-python@v6
|
|
77
|
+
with:
|
|
78
|
+
python-version: "3.11"
|
|
79
|
+
|
|
80
|
+
- name: Install uv
|
|
81
|
+
uses: astral-sh/setup-uv@v8.2.0
|
|
82
|
+
with:
|
|
83
|
+
enable-cache: true
|
|
84
|
+
|
|
85
|
+
- name: Set project version
|
|
86
|
+
run: |
|
|
87
|
+
uv version ${{ needs.details.outputs.new_version }}
|
|
88
|
+
|
|
89
|
+
- name: Install dependencies
|
|
90
|
+
run: uv sync
|
|
91
|
+
|
|
92
|
+
- name: Build source publish
|
|
93
|
+
env:
|
|
94
|
+
PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
95
|
+
run: |
|
|
96
|
+
uv build
|
|
97
|
+
uv publish --token $PYPI_TOKEN
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
name: pull_request
|
|
2
|
+
on: [pull_request]
|
|
3
|
+
jobs:
|
|
4
|
+
lint:
|
|
5
|
+
name: "Lint"
|
|
6
|
+
runs-on: ubuntu-latest
|
|
7
|
+
steps:
|
|
8
|
+
- run: echo "Running lint for ${{ github.ref }} branch"
|
|
9
|
+
|
|
10
|
+
- name: Check out repository code
|
|
11
|
+
uses: actions/checkout@v7
|
|
12
|
+
with:
|
|
13
|
+
fetch-depth: 1
|
|
14
|
+
|
|
15
|
+
- name: Install uv
|
|
16
|
+
uses: astral-sh/setup-uv@v8.2.0
|
|
17
|
+
with:
|
|
18
|
+
enable-cache: true
|
|
19
|
+
|
|
20
|
+
- name: Install dependencies
|
|
21
|
+
run: uv sync --dev
|
|
22
|
+
|
|
23
|
+
- name: Running Lint
|
|
24
|
+
run: uv run --with ruff ruff check .
|
|
25
|
+
|
|
26
|
+
run_tests:
|
|
27
|
+
name: "Run Tests"
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
steps:
|
|
30
|
+
- run: echo "Running tests for ${{ github.ref }} branch"
|
|
31
|
+
|
|
32
|
+
- name: Check out repository code
|
|
33
|
+
uses: actions/checkout@v7
|
|
34
|
+
with:
|
|
35
|
+
fetch-depth: 1
|
|
36
|
+
|
|
37
|
+
- name: Install uv
|
|
38
|
+
uses: astral-sh/setup-uv@v8.2.0
|
|
39
|
+
with:
|
|
40
|
+
enable-cache: true
|
|
41
|
+
|
|
42
|
+
- name: Install dependencies
|
|
43
|
+
run: uv sync --dev
|
|
44
|
+
|
|
45
|
+
- name: Running tests
|
|
46
|
+
run: uv run --with pytest --with pytest-cov pytest --cov-report=term --cov .
|
|
47
|
+
|
|
48
|
+
mypy:
|
|
49
|
+
name: "Mypy Validation"
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
steps:
|
|
52
|
+
- name: Check out repository code
|
|
53
|
+
uses: actions/checkout@v7
|
|
54
|
+
with:
|
|
55
|
+
fetch-depth: 1
|
|
56
|
+
|
|
57
|
+
- name: Install uv
|
|
58
|
+
uses: astral-sh/setup-uv@v8.2.0
|
|
59
|
+
with:
|
|
60
|
+
enable-cache: true
|
|
61
|
+
|
|
62
|
+
- name: Install dependencies
|
|
63
|
+
run: uv sync --dev
|
|
64
|
+
|
|
65
|
+
- name: Running Mypy
|
|
66
|
+
run: uv run --with mypy python3 -m mypy src/ tests/ --follow-untyped-imports --explicit-package-bases --check-untyped-defs
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
*.egg
|
|
8
|
+
.eggs/
|
|
9
|
+
|
|
10
|
+
# Virtual environment
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
|
|
14
|
+
# IDE
|
|
15
|
+
.vscode/
|
|
16
|
+
.idea/
|
|
17
|
+
|
|
18
|
+
# OS
|
|
19
|
+
.DS_Store
|
|
20
|
+
|
|
21
|
+
# Coverage
|
|
22
|
+
.coverage
|
|
23
|
+
htmlcov/
|
|
24
|
+
coverage/
|
|
25
|
+
|
|
26
|
+
# Environment
|
|
27
|
+
.env
|
|
28
|
+
|
|
29
|
+
# Local tooling
|
|
30
|
+
token-optimizer/
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aqi-in-api
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Python SDK for the AQI.in Air Quality API
|
|
5
|
+
Project-URL: Repository, https://github.com/GuyKh/py-aqi-in-api
|
|
6
|
+
Project-URL: Issues, https://github.com/GuyKh/py-aqi-in-api/issues
|
|
7
|
+
Author: GuyKh
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Requires-Dist: httpx>=0.28.0
|
|
11
|
+
Requires-Dist: pyjwt>=2.10.0
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# py-aqi-in-api
|
|
15
|
+
|
|
16
|
+
Python SDK for the [AQI.in](https://aqi.in) Air Quality API.
|
|
17
|
+
|
|
18
|
+
Fully typed, async, using modern Python (3.11+), dataclasses, and httpx.
|
|
19
|
+
|
|
20
|
+
> **Source**: This is a Python port of the [aqi-in-api](https://github.com/neo773/aqi-in-api) TypeScript SDK by [@neo773](https://github.com/neo773).
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install py-aqi-in-api
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Requires Python 3.11+.
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import asyncio
|
|
34
|
+
|
|
35
|
+
from aqi_in_api import AQIClient
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def main() -> None:
|
|
39
|
+
client = AQIClient()
|
|
40
|
+
|
|
41
|
+
ip_details = await client.get_ip_details()
|
|
42
|
+
print(f"Location: {ip_details.city}, {ip_details.country}")
|
|
43
|
+
|
|
44
|
+
nearest = await client.get_nearest_location(
|
|
45
|
+
lat=ip_details.lat, long=ip_details.lon,
|
|
46
|
+
)
|
|
47
|
+
station = nearest[0]
|
|
48
|
+
print(f"Nearest station: {station.station} ({station.location_slug})")
|
|
49
|
+
|
|
50
|
+
location = await client.get_location_by_slug(slug=station.location_slug)
|
|
51
|
+
print(f"Location AQI: {location[0].iaqi}")
|
|
52
|
+
|
|
53
|
+
history = await client.get_last_24_hour_history(
|
|
54
|
+
slug=station.location_slug, sensorname="pm25", slug_type="locationId",
|
|
55
|
+
)
|
|
56
|
+
print(f"24h PM2.5 avg: {history.avgValue}")
|
|
57
|
+
|
|
58
|
+
await client.close()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
asyncio.run(main())
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## API
|
|
65
|
+
|
|
66
|
+
### `create_aqi_client(config?)`
|
|
67
|
+
|
|
68
|
+
| Option | Type | Required | Description |
|
|
69
|
+
|--------|------|----------|-------------|
|
|
70
|
+
| `token` | `str \| None` | No | JWT authentication token (auto-generated if omitted) |
|
|
71
|
+
| `base_url` | `str` | No | API base URL (default: `https://apiserver.aqi.in`) |
|
|
72
|
+
| `user_agent` | `str` | No | Custom user agent |
|
|
73
|
+
|
|
74
|
+
### Methods
|
|
75
|
+
|
|
76
|
+
All methods take keyword-only arguments. No `*Params` objects needed.
|
|
77
|
+
|
|
78
|
+
| Method | Keyword Args | Returns | Description |
|
|
79
|
+
|--------|-------------|---------|-------------|
|
|
80
|
+
| `get_ip_details()` | — | `IPDetails` | Get location from your IP address |
|
|
81
|
+
| `get_nearest_location(**kwargs)` | `lat`, `long`, `type?` | `list[Station]` | Get nearest monitoring stations by coordinates |
|
|
82
|
+
| `get_location_by_slug(**kwargs)` | `slug`, `type?` | `list[LocationDetails]` | Get location details by slug |
|
|
83
|
+
| `search(**kwargs)` | `search_string` | `SearchResults` | Search locations by name |
|
|
84
|
+
| `get_last_12_hour_history(**kwargs)` | `slug`, `sensorname`, `slug_type` | `HistoryData` | Get 12-hour sensor history |
|
|
85
|
+
| `get_last_24_hour_history(**kwargs)` | `slug`, `sensorname`, `slug_type` | `HistoryDataWithWHO` | Get 24-hour history with WHO guidelines |
|
|
86
|
+
| `get_last_7_days_history(**kwargs)` | `slug`, `sensorname`, `slug_type` | `HistoryDataWithWHO` | Get 7-day sensor history |
|
|
87
|
+
| `get_last_30_days_history(**kwargs)` | `slug`, `sensorname`, `slug_type` | `HistoryDataWithWHO` | Get 30-day sensor history |
|
|
88
|
+
| `get_rankings(**kwargs)` | `sensorname`, `type`, `limit=10` | `list[RankingEntry]` | Get city or country pollution rankings |
|
|
89
|
+
| `close()` | — | `None` | Close the underlying HTTP client |
|
|
90
|
+
|
|
91
|
+
### Types
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from aqi_in_api.models import (
|
|
95
|
+
Station, City, State, Country,
|
|
96
|
+
LocationDetails, IPDetails, SearchResults, RankingEntry,
|
|
97
|
+
HistoryData, HistoryDataWithWHO,
|
|
98
|
+
IAQI, Weather, WeatherCondition, WeatherSimple, UVCondition,
|
|
99
|
+
SensorName, SearchType, SlugType, LocationType, RankType,
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Development
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
git clone https://github.com/GuyKh/py-aqi-in-api
|
|
107
|
+
cd py-aqi-in-api
|
|
108
|
+
uv sync --dev
|
|
109
|
+
uv run pytest
|
|
110
|
+
uv run ruff check .
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT
|
|
116
|
+
|
|
117
|
+
## Disclaimer
|
|
118
|
+
|
|
119
|
+
This is an **unofficial** API client and is not affiliated with, endorsed by, or associated with AQI.in or its parent organization. This package is provided for educational and informational purposes under fair use.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# py-aqi-in-api
|
|
2
|
+
|
|
3
|
+
Python SDK for the [AQI.in](https://aqi.in) Air Quality API.
|
|
4
|
+
|
|
5
|
+
Fully typed, async, using modern Python (3.11+), dataclasses, and httpx.
|
|
6
|
+
|
|
7
|
+
> **Source**: This is a Python port of the [aqi-in-api](https://github.com/neo773/aqi-in-api) TypeScript SDK by [@neo773](https://github.com/neo773).
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install py-aqi-in-api
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires Python 3.11+.
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import asyncio
|
|
21
|
+
|
|
22
|
+
from aqi_in_api import AQIClient
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def main() -> None:
|
|
26
|
+
client = AQIClient()
|
|
27
|
+
|
|
28
|
+
ip_details = await client.get_ip_details()
|
|
29
|
+
print(f"Location: {ip_details.city}, {ip_details.country}")
|
|
30
|
+
|
|
31
|
+
nearest = await client.get_nearest_location(
|
|
32
|
+
lat=ip_details.lat, long=ip_details.lon,
|
|
33
|
+
)
|
|
34
|
+
station = nearest[0]
|
|
35
|
+
print(f"Nearest station: {station.station} ({station.location_slug})")
|
|
36
|
+
|
|
37
|
+
location = await client.get_location_by_slug(slug=station.location_slug)
|
|
38
|
+
print(f"Location AQI: {location[0].iaqi}")
|
|
39
|
+
|
|
40
|
+
history = await client.get_last_24_hour_history(
|
|
41
|
+
slug=station.location_slug, sensorname="pm25", slug_type="locationId",
|
|
42
|
+
)
|
|
43
|
+
print(f"24h PM2.5 avg: {history.avgValue}")
|
|
44
|
+
|
|
45
|
+
await client.close()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
asyncio.run(main())
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## API
|
|
52
|
+
|
|
53
|
+
### `create_aqi_client(config?)`
|
|
54
|
+
|
|
55
|
+
| Option | Type | Required | Description |
|
|
56
|
+
|--------|------|----------|-------------|
|
|
57
|
+
| `token` | `str \| None` | No | JWT authentication token (auto-generated if omitted) |
|
|
58
|
+
| `base_url` | `str` | No | API base URL (default: `https://apiserver.aqi.in`) |
|
|
59
|
+
| `user_agent` | `str` | No | Custom user agent |
|
|
60
|
+
|
|
61
|
+
### Methods
|
|
62
|
+
|
|
63
|
+
All methods take keyword-only arguments. No `*Params` objects needed.
|
|
64
|
+
|
|
65
|
+
| Method | Keyword Args | Returns | Description |
|
|
66
|
+
|--------|-------------|---------|-------------|
|
|
67
|
+
| `get_ip_details()` | — | `IPDetails` | Get location from your IP address |
|
|
68
|
+
| `get_nearest_location(**kwargs)` | `lat`, `long`, `type?` | `list[Station]` | Get nearest monitoring stations by coordinates |
|
|
69
|
+
| `get_location_by_slug(**kwargs)` | `slug`, `type?` | `list[LocationDetails]` | Get location details by slug |
|
|
70
|
+
| `search(**kwargs)` | `search_string` | `SearchResults` | Search locations by name |
|
|
71
|
+
| `get_last_12_hour_history(**kwargs)` | `slug`, `sensorname`, `slug_type` | `HistoryData` | Get 12-hour sensor history |
|
|
72
|
+
| `get_last_24_hour_history(**kwargs)` | `slug`, `sensorname`, `slug_type` | `HistoryDataWithWHO` | Get 24-hour history with WHO guidelines |
|
|
73
|
+
| `get_last_7_days_history(**kwargs)` | `slug`, `sensorname`, `slug_type` | `HistoryDataWithWHO` | Get 7-day sensor history |
|
|
74
|
+
| `get_last_30_days_history(**kwargs)` | `slug`, `sensorname`, `slug_type` | `HistoryDataWithWHO` | Get 30-day sensor history |
|
|
75
|
+
| `get_rankings(**kwargs)` | `sensorname`, `type`, `limit=10` | `list[RankingEntry]` | Get city or country pollution rankings |
|
|
76
|
+
| `close()` | — | `None` | Close the underlying HTTP client |
|
|
77
|
+
|
|
78
|
+
### Types
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from aqi_in_api.models import (
|
|
82
|
+
Station, City, State, Country,
|
|
83
|
+
LocationDetails, IPDetails, SearchResults, RankingEntry,
|
|
84
|
+
HistoryData, HistoryDataWithWHO,
|
|
85
|
+
IAQI, Weather, WeatherCondition, WeatherSimple, UVCondition,
|
|
86
|
+
SensorName, SearchType, SlugType, LocationType, RankType,
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Development
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
git clone https://github.com/GuyKh/py-aqi-in-api
|
|
94
|
+
cd py-aqi-in-api
|
|
95
|
+
uv sync --dev
|
|
96
|
+
uv run pytest
|
|
97
|
+
uv run ruff check .
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT
|
|
103
|
+
|
|
104
|
+
## Disclaimer
|
|
105
|
+
|
|
106
|
+
This is an **unofficial** API client and is not affiliated with, endorsed by, or associated with AQI.in or its parent organization. This package is provided for educational and informational purposes under fair use.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from aqi_in_api import create_aqi_client
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def main() -> None:
|
|
7
|
+
client = create_aqi_client()
|
|
8
|
+
|
|
9
|
+
ip_details = await client.get_ip_details()
|
|
10
|
+
print(ip_details)
|
|
11
|
+
|
|
12
|
+
nearest_location = await client.get_nearest_location(
|
|
13
|
+
lat=ip_details.lat,
|
|
14
|
+
long=ip_details.lon,
|
|
15
|
+
)
|
|
16
|
+
print(nearest_location)
|
|
17
|
+
|
|
18
|
+
station = nearest_location[0].location_slug
|
|
19
|
+
|
|
20
|
+
location_details = await client.get_location_by_slug(slug=station)
|
|
21
|
+
print(location_details)
|
|
22
|
+
|
|
23
|
+
history = await client.get_last_24_hour_history(
|
|
24
|
+
slug=station,
|
|
25
|
+
sensorname="pm25",
|
|
26
|
+
slug_type="locationId",
|
|
27
|
+
)
|
|
28
|
+
print(history)
|
|
29
|
+
|
|
30
|
+
history_30_days = await client.get_last_30_days_history(
|
|
31
|
+
slug=station,
|
|
32
|
+
sensorname="pm25",
|
|
33
|
+
slug_type="locationId",
|
|
34
|
+
)
|
|
35
|
+
print(history_30_days)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "aqi-in-api"
|
|
3
|
+
version = "0.0.2"
|
|
4
|
+
description = "Python SDK for the AQI.in Air Quality API"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "GuyKh" },
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
dependencies = [
|
|
13
|
+
"httpx>=0.28.0",
|
|
14
|
+
"pyjwt>=2.10.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Repository = "https://github.com/GuyKh/py-aqi-in-api"
|
|
19
|
+
Issues = "https://github.com/GuyKh/py-aqi-in-api/issues"
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["hatchling"]
|
|
23
|
+
build-backend = "hatchling.build"
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel]
|
|
26
|
+
packages = ["src/aqi_in_api"]
|
|
27
|
+
|
|
28
|
+
[tool.ruff]
|
|
29
|
+
line-length = 100
|
|
30
|
+
target-version = "py311"
|
|
31
|
+
|
|
32
|
+
[tool.ruff.lint]
|
|
33
|
+
select = ["E", "F", "I", "N", "W"]
|
|
34
|
+
|
|
35
|
+
[tool.ruff.lint.per-file-ignores]
|
|
36
|
+
"src/aqi_in_api/models.py" = ["N815"]
|
|
37
|
+
"src/aqi_in_api/_exceptions.py" = ["N818"]
|
|
38
|
+
|
|
39
|
+
[tool.mypy]
|
|
40
|
+
strict = true
|
|
41
|
+
python_version = "3.11"
|
|
42
|
+
namespace_packages = true
|
|
43
|
+
explicit_package_bases = true
|
|
44
|
+
mypy_path = ["src/"]
|
|
45
|
+
follow_untyped_imports = true
|
|
46
|
+
check_untyped_defs = true
|
|
47
|
+
|
|
48
|
+
[[tool.mypy.overrides]]
|
|
49
|
+
module = "pytest_httpx"
|
|
50
|
+
ignore_missing_imports = true
|
|
51
|
+
|
|
52
|
+
[tool.pytest.ini_options]
|
|
53
|
+
asyncio_mode = "auto"
|
|
54
|
+
testpaths = ["tests"]
|
|
55
|
+
|
|
56
|
+
[dependency-groups]
|
|
57
|
+
dev = [
|
|
58
|
+
"pytest>=9.1.1",
|
|
59
|
+
"pytest-asyncio>=1.4.0",
|
|
60
|
+
"pytest-httpx>=0.36.2",
|
|
61
|
+
"ruff>=0.15.20",
|
|
62
|
+
]
|