aqi-in-api 0.0.2__py3-none-any.whl

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/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ from aqi_in_api._client import AQIClient, ClientConfig, create_aqi_client
2
+ from aqi_in_api._exceptions import AQIException
3
+
4
+ __all__ = [
5
+ "AQIClient",
6
+ "AQIException",
7
+ "ClientConfig",
8
+ "create_aqi_client",
9
+ ]
aqi_in_api/_client.py ADDED
@@ -0,0 +1,238 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import typing
5
+ from dataclasses import dataclass
6
+ from typing import Any, TypeVar
7
+
8
+ import httpx
9
+
10
+ from aqi_in_api._constants import DEFAULT_BASE_URL, DEFAULT_USER_AGENT, ENDPOINTS
11
+ from aqi_in_api._exceptions import AQIException
12
+ from aqi_in_api._token import generate_token
13
+ from aqi_in_api._utils import build_url, get_slug_depth
14
+ from aqi_in_api.models import (
15
+ HistoryData,
16
+ HistoryDataWithWHO,
17
+ IPDetails,
18
+ LocationDetails,
19
+ LocationType,
20
+ RankingEntry,
21
+ RankType,
22
+ SearchResults,
23
+ SearchType,
24
+ SensorName,
25
+ SlugType,
26
+ Station,
27
+ )
28
+
29
+ T = TypeVar("T")
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class ClientConfig:
34
+ token: str | None = None
35
+ base_url: str = DEFAULT_BASE_URL
36
+ user_agent: str = DEFAULT_USER_AGENT
37
+
38
+
39
+ def _from_dict(model_cls: type[T], data: dict[str, Any]) -> T:
40
+ """Convert a nested dict into a frozen dataclass instance, skipping unknown fields."""
41
+ if not dataclasses.is_dataclass(model_cls) or not isinstance(data, dict):
42
+ return data # type: ignore[return-value]
43
+ kwargs: dict[str, Any] = {}
44
+ for field in dataclasses.fields(model_cls):
45
+ if field.name not in data:
46
+ continue
47
+ kwargs[field.name] = _convert_value(field.type, data[field.name])
48
+ return model_cls(**kwargs)
49
+
50
+
51
+ def _convert_value(field_type: object, value: Any) -> Any:
52
+ if value is None:
53
+ return None
54
+ origin = typing.get_origin(field_type)
55
+ args = typing.get_args(field_type)
56
+ if origin is typing.Union:
57
+ non_none = [a for a in args if a is not type(None)]
58
+ if non_none:
59
+ return _convert_value(non_none[0], value)
60
+ return None
61
+ if origin is list and args and isinstance(value, list):
62
+ return [_convert_value(args[0], item) for item in value]
63
+ if origin is tuple and args and isinstance(value, (list, tuple)):
64
+ return tuple(_convert_value(args[i], v) for i, v in enumerate(value[: len(args)]))
65
+ try:
66
+ if isinstance(value, dict) and dataclasses.is_dataclass(field_type):
67
+ return _from_dict(field_type, value) # type: ignore[arg-type]
68
+ except TypeError:
69
+ pass
70
+ return value
71
+
72
+
73
+ def _to_model(model_cls: type[T], data: Any) -> Any:
74
+ if isinstance(data, list):
75
+ return [_from_dict(model_cls, item) for item in data]
76
+ return _from_dict(model_cls, data)
77
+
78
+
79
+ class AQIClient:
80
+ def __init__(self, config: ClientConfig | None = None) -> None:
81
+ config = config or ClientConfig()
82
+ self._base_url = config.base_url
83
+ self._custom_token = config.token
84
+ self._user_agent = config.user_agent
85
+ self._cached_token: str | None = None
86
+ self._http = httpx.AsyncClient()
87
+
88
+ async def _get_token(self) -> str:
89
+ if self._custom_token is not None:
90
+ return self._custom_token
91
+ if self._cached_token is None:
92
+ self._cached_token = generate_token()
93
+ return self._cached_token
94
+
95
+ async def _request(
96
+ self,
97
+ endpoint: str,
98
+ params: dict[str, str | int | float | None] | None = None,
99
+ ) -> Any:
100
+ url = build_url(self._base_url, endpoint, params)
101
+ token = await self._get_token()
102
+
103
+ response = await self._http.get(
104
+ url,
105
+ headers={
106
+ "User-Agent": self._user_agent,
107
+ "authorization": f"bearer {token}",
108
+ },
109
+ )
110
+
111
+ if not response.is_success:
112
+ raise AQIException(
113
+ f"Request failed: {response.status_code} {response.reason_phrase}",
114
+ response.status_code,
115
+ response.text,
116
+ )
117
+
118
+ body: dict[str, Any] = response.json()
119
+
120
+ if body.get("status") in ("failed",):
121
+ raise AQIException(
122
+ body.get("message") or body.get("error") or "Unknown error",
123
+ body.get("status_code", 400),
124
+ )
125
+
126
+ return body["data"]
127
+
128
+ async def get_ip_details(self) -> IPDetails:
129
+ data = await self._request(ENDPOINTS["IP_DETAILS"])
130
+ return _from_dict(IPDetails, data)
131
+
132
+ async def get_nearest_location(
133
+ self,
134
+ *,
135
+ lat: float,
136
+ long: float,
137
+ type: LocationType | None = None,
138
+ ) -> list[Station]:
139
+ data = await self._request(
140
+ ENDPOINTS["NEAREST_LOCATION"],
141
+ {"lat": lat, "long": long, "type": "2" if type == "city" else "1"},
142
+ )
143
+ return _to_model(Station, data) # type: ignore[no-any-return]
144
+
145
+ async def get_location_by_slug(
146
+ self,
147
+ *,
148
+ slug: str,
149
+ type: SlugType | None = None,
150
+ ) -> list[LocationDetails]:
151
+ data = await self._request(
152
+ ENDPOINTS["LOCATION_BY_SLUG"],
153
+ {
154
+ "slug": slug,
155
+ "type": type if type is not None else get_slug_depth(slug),
156
+ },
157
+ )
158
+ return _to_model(LocationDetails, data) # type: ignore[no-any-return]
159
+
160
+ async def search(self, *, search_string: str) -> SearchResults:
161
+ data = await self._request(ENDPOINTS["SEARCH"], {"searchString": search_string})
162
+ return _from_dict(SearchResults, data)
163
+
164
+ async def get_last_12_hour_history(
165
+ self,
166
+ *,
167
+ slug: str,
168
+ sensorname: SensorName,
169
+ slug_type: SearchType,
170
+ ) -> HistoryData:
171
+ data = await self._request(
172
+ ENDPOINTS["HISTORY_12H"],
173
+ {"slug": slug, "sensorname": sensorname, "slugType": slug_type},
174
+ )
175
+ return _from_dict(HistoryData, data)
176
+
177
+ async def get_last_24_hour_history(
178
+ self,
179
+ *,
180
+ slug: str,
181
+ sensorname: SensorName,
182
+ slug_type: SearchType,
183
+ ) -> HistoryDataWithWHO:
184
+ data = await self._request(
185
+ ENDPOINTS["HISTORY_24H"],
186
+ {"slug": slug, "sensorname": sensorname, "slugType": slug_type},
187
+ )
188
+ return _from_dict(HistoryDataWithWHO, data)
189
+
190
+ async def get_last_7_days_history(
191
+ self,
192
+ *,
193
+ slug: str,
194
+ sensorname: SensorName,
195
+ slug_type: SearchType,
196
+ ) -> HistoryDataWithWHO:
197
+ data = await self._request(
198
+ ENDPOINTS["HISTORY_7D"],
199
+ {"slug": slug, "sensorname": sensorname, "slugType": slug_type},
200
+ )
201
+ return _from_dict(HistoryDataWithWHO, data)
202
+
203
+ async def get_last_30_days_history(
204
+ self,
205
+ *,
206
+ slug: str,
207
+ sensorname: SensorName,
208
+ slug_type: SearchType,
209
+ ) -> HistoryDataWithWHO:
210
+ data = await self._request(
211
+ ENDPOINTS["HISTORY_30D"],
212
+ {"slug": slug, "sensorname": sensorname, "slugType": slug_type},
213
+ )
214
+ return _from_dict(HistoryDataWithWHO, data)
215
+
216
+ async def get_rankings(
217
+ self,
218
+ *,
219
+ sensorname: SensorName,
220
+ type: RankType,
221
+ limit: int = 10,
222
+ ) -> list[RankingEntry]:
223
+ data = await self._request(
224
+ ENDPOINTS["RANKINGS"],
225
+ {
226
+ "sensorname": sensorname,
227
+ "type": "1" if type == "country" else "2",
228
+ "limit": limit,
229
+ },
230
+ )
231
+ return _to_model(RankingEntry, data) # type: ignore[no-any-return]
232
+
233
+ async def close(self) -> None:
234
+ await self._http.aclose()
235
+
236
+
237
+ def create_aqi_client(config: ClientConfig | None = None) -> AQIClient:
238
+ return AQIClient(config)
@@ -0,0 +1,19 @@
1
+ DEFAULT_BASE_URL = "https://apiserver.aqi.in"
2
+
3
+ DEFAULT_USER_AGENT = (
4
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
5
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
6
+ "Chrome/143.0.0.0 Safari/537.36"
7
+ )
8
+
9
+ ENDPOINTS = {
10
+ "NEAREST_LOCATION": "/aqi/v2/getNearestLocation",
11
+ "IP_DETAILS": "/service/get/ip/details",
12
+ "LOCATION_BY_SLUG": "/aqi/v2/getLocationDetailsBySlug",
13
+ "SEARCH": "/aqi/searchLocationCityStateCountry",
14
+ "HISTORY_12H": "/aqi/getLast12HourHistory",
15
+ "HISTORY_24H": "/aqi/v3/getLast24HourHistory",
16
+ "HISTORY_7D": "/aqi/getLast7DaysHistory",
17
+ "HISTORY_30D": "/aqi/getLast30DaysHistory",
18
+ "RANKINGS": "/aqi/getAirQualityRanklistCountryAndCity",
19
+ }
@@ -0,0 +1,12 @@
1
+ class AQIException(Exception):
2
+ def __init__(self, message: str, status_code: int, body: str | None = None) -> None:
3
+ super().__init__(message)
4
+ self.message = message
5
+ self.status_code = status_code
6
+ self.body = body
7
+ self.name = "AQIException"
8
+
9
+ def __str__(self) -> str:
10
+ if self.body:
11
+ return f"AQIException({self.status_code}): {self.message} - {self.body}"
12
+ return f"AQIException({self.status_code}): {self.message}"
aqi_in_api/_token.py ADDED
@@ -0,0 +1,17 @@
1
+ import time
2
+
3
+ import jwt
4
+
5
+ JWT_SECRET = "masai"
6
+ TOKEN_EXPIRY_SECONDS = 7 * 24 * 60 * 60
7
+
8
+
9
+ def generate_token() -> str:
10
+ """Generate a JWT token matching the AQI.in API expectations."""
11
+ now = int(time.time())
12
+ payload = {
13
+ "userID": 1,
14
+ "iat": now,
15
+ "exp": now + TOKEN_EXPIRY_SECONDS,
16
+ }
17
+ return jwt.encode(payload, JWT_SECRET, algorithm="HS256")
aqi_in_api/_utils.py ADDED
@@ -0,0 +1,34 @@
1
+ from collections.abc import Mapping
2
+ from urllib.parse import urlencode, urljoin
3
+
4
+ from aqi_in_api.models import SlugType
5
+
6
+
7
+ def build_url(
8
+ base_url: str,
9
+ endpoint: str,
10
+ params: Mapping[str, str | int | float | None] | None = None,
11
+ ) -> str:
12
+ """Build a URL from base, endpoint, and query parameters.
13
+
14
+ Always appends ?source=web to match the TS SDK behavior.
15
+ """
16
+ url = urljoin(base_url.rstrip("/") + "/", endpoint.lstrip("/"))
17
+ query_params: dict[str, str] = {"source": "web"}
18
+
19
+ if params:
20
+ for key, value in params.items():
21
+ if value is not None:
22
+ query_params[key] = str(value)
23
+
24
+ return f"{url}?{urlencode(query_params)}"
25
+
26
+
27
+ def get_slug_depth(slug: str) -> SlugType:
28
+ """Determine the slug depth (1=country, 2=state, 3=city, 4=station)."""
29
+ depth = slug.count("/") + 1
30
+ if depth < 1:
31
+ return 1
32
+ if depth > 4:
33
+ return 4
34
+ return depth # type: ignore[return-value]
aqi_in_api/models.py ADDED
@@ -0,0 +1,229 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Literal
5
+
6
+ SensorName = Literal["pm25", "pm10", "aqi", "AQI-IN", "co", "no2", "o3", "so2"]
7
+ SearchType = Literal["locationId", "cityId", "stateId", "countryId"]
8
+ LocationType = Literal["station", "city"]
9
+ SlugType = Literal[1, 2, 3, 4]
10
+ RankType = Literal["city", "country"]
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class IAQI:
15
+ AQI_IN: int | None = None
16
+ aqi: int | None = None
17
+ pm25: int | None = None
18
+ pm10: int | None = None
19
+ co: int | None = None
20
+ no2: int | None = None
21
+ o3: int | None = None
22
+ so2: int | None = None
23
+ noise: int | None = None
24
+ tvoc: int | None = None
25
+ t: float | None = None
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class WeatherCondition:
30
+ text: str
31
+ icon: str
32
+ code: int
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class UVCondition:
37
+ text: str
38
+ color_code: str
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class Weather:
43
+ uid: str
44
+ cloud: int
45
+ condition: WeatherCondition
46
+ feelslike_c: float
47
+ feelslike_f: float
48
+ gust_kph: float
49
+ gust_mph: float
50
+ humidity: int
51
+ is_day: int
52
+ last_updated: str
53
+ last_updated_epoch: int
54
+ precip_in: float
55
+ precip_mm: float
56
+ pressure_in: float
57
+ pressure_mb: float
58
+ temp_c: float
59
+ temp_f: float
60
+ uv: float
61
+ vis_km: float
62
+ vis_miles: float
63
+ wind_degree: int
64
+ wind_dir: str
65
+ wind_kph: float
66
+ wind_mph: float
67
+ uv_condition: UVCondition | None = None
68
+
69
+
70
+ @dataclass(frozen=True)
71
+ class WeatherSimple:
72
+ temp_c: float
73
+ temp_f: float
74
+
75
+
76
+ @dataclass(frozen=True, kw_only=True)
77
+ class BaseLocation:
78
+ location: str
79
+ locationId: str
80
+ slug: str
81
+ latitude: float
82
+ longitude: float
83
+ flag: str
84
+ searchType: SearchType
85
+ uid: str | None = None
86
+
87
+
88
+ @dataclass(frozen=True)
89
+ class Station(BaseLocation):
90
+ station: str
91
+ city: str
92
+ state: str
93
+ country: str
94
+ location_slug: str
95
+ city_slug: str
96
+ state_slug: str
97
+ country_slug: str
98
+ time_zone: str
99
+ coordinates: tuple[float, float]
100
+ background_image: str
101
+ city_lat: float
102
+ city_lon: float
103
+ state_lat: float
104
+ state_lon: float
105
+ country_lat: float
106
+ country_lon: float
107
+ isOnline: bool
108
+ isRankedCity: bool
109
+ iaqi: IAQI
110
+ updated_at: str
111
+ weather: Weather | None = None
112
+ updatedAt: str | None = None
113
+ createdAt: str | None = None
114
+ distance: float | None = None
115
+ source: str | None = None
116
+
117
+
118
+ @dataclass(frozen=True)
119
+ class City(BaseLocation):
120
+ city: str
121
+ state: str
122
+ country: str
123
+ weather: WeatherSimple | None = None
124
+ iaqi: IAQI | None = None
125
+
126
+
127
+ @dataclass(frozen=True)
128
+ class State(BaseLocation):
129
+ state: str
130
+ country: str
131
+
132
+
133
+ @dataclass(frozen=True)
134
+ class Country(BaseLocation):
135
+ pass
136
+
137
+
138
+ @dataclass(frozen=True)
139
+ class IPDetails:
140
+ city: str
141
+ country: str
142
+ countryCode: str
143
+ lat: float
144
+ lon: float
145
+ offset: int
146
+ query: str
147
+ regionName: str
148
+ status: str
149
+ timezone: str
150
+ zip: str
151
+
152
+
153
+ @dataclass(frozen=True)
154
+ class LocationDetails:
155
+ uid: str
156
+ location: str
157
+ country: str
158
+ time_zone: str
159
+ latitude: float
160
+ longitude: float
161
+ country_slug: str
162
+ background_image: str
163
+ flag: str
164
+ country_lat: float
165
+ country_lon: float
166
+ isOnline: bool
167
+ isRankedCity: bool
168
+ iaqi: IAQI
169
+ updated_at: str
170
+ locationId: str
171
+ searchType: SearchType
172
+ slug: str
173
+ station: str | None = None
174
+ city: str | None = None
175
+ state: str | None = None
176
+ location_slug: str | None = None
177
+ city_slug: str | None = None
178
+ state_slug: str | None = None
179
+ city_lat: float | None = None
180
+ city_lon: float | None = None
181
+ state_lat: float | None = None
182
+ state_lon: float | None = None
183
+ weather: Weather | None = None
184
+ updatedAt: str | None = None
185
+
186
+
187
+ @dataclass(frozen=True)
188
+ class HistoryData:
189
+ minValue: float
190
+ maxValue: float
191
+ avgValue: float
192
+ averageArray: list[float]
193
+ timeArray: list[str]
194
+
195
+
196
+ @dataclass(frozen=True)
197
+ class WHOGuideData:
198
+ Data: HistoryData
199
+
200
+
201
+ @dataclass(frozen=True)
202
+ class HistoryDataWithWHO(HistoryData):
203
+ whoguidedata: WHOGuideData | None = None
204
+
205
+
206
+ @dataclass(frozen=True)
207
+ class SearchResults:
208
+ countries: list[Country]
209
+ states: list[State]
210
+ cities: list[City]
211
+ stations: list[Station]
212
+
213
+
214
+ @dataclass(frozen=True)
215
+ class RankingEntry:
216
+ location: str
217
+ locationId: str
218
+ flag: str
219
+ slug: str
220
+ latitude: float
221
+ longitude: float
222
+ updated_at: str
223
+ rank: int
224
+ city: str | None = None
225
+ state: str | None = None
226
+ country: str | None = None
227
+ pm25: float | None = None
228
+ pm10: float | None = None
229
+ aqi: float | None = None
@@ -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,10 @@
1
+ aqi_in_api/__init__.py,sha256=tOi5xmSkkC73UJ0O_SWO5yCPxr-jm8XIsIN7CXcP5Fg,219
2
+ aqi_in_api/_client.py,sha256=JAlDVsFHx8SO47Nb95tpc2ULcc0UNZHDhuyW7hsMqac,7358
3
+ aqi_in_api/_constants.py,sha256=HQQPFkx2YCZLvkLyh-6ETM0dvcUIwOxYxficu8jKFH4,692
4
+ aqi_in_api/_exceptions.py,sha256=er7r1oTU_deiN41HsEsJJVKOn_vuoM_eI0QMcYZ92Lw,489
5
+ aqi_in_api/_token.py,sha256=bW4W1T2zJ81VP2Sa2wK39xVpqNgXMdvgYn_w7S1T8Ao,381
6
+ aqi_in_api/_utils.py,sha256=YZU3LHL46hl5MuQf00jrHtmS4_g1O8AFLRufoqGiFy8,960
7
+ aqi_in_api/models.py,sha256=OrhVBX1QTWT_tWk2m-6q18EATaAiYNwJY_HlSBjNhDk,4593
8
+ aqi_in_api-0.0.2.dist-info/METADATA,sha256=Su3lS1yPGCxxhXOPTsdhjaDt0nRmoLSrXNXLkFCFQPk,3944
9
+ aqi_in_api-0.0.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
10
+ aqi_in_api-0.0.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any