ptr727-aiopurpleair 1.0.0.dev0__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.
Files changed (55) hide show
  1. ptr727_aiopurpleair-1.0.0.dev0/.gitignore +34 -0
  2. ptr727_aiopurpleair-1.0.0.dev0/LICENSE +22 -0
  3. ptr727_aiopurpleair-1.0.0.dev0/NOTICE +23 -0
  4. ptr727_aiopurpleair-1.0.0.dev0/PKG-INFO +346 -0
  5. ptr727_aiopurpleair-1.0.0.dev0/README.md +314 -0
  6. ptr727_aiopurpleair-1.0.0.dev0/pyproject.toml +115 -0
  7. ptr727_aiopurpleair-1.0.0.dev0/repo-config/README.md +64 -0
  8. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/__init__.py +6 -0
  9. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/_version.py +3 -0
  10. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/api.py +121 -0
  11. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/const.py +160 -0
  12. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/endpoints/__init__.py +65 -0
  13. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/endpoints/organizations.py +27 -0
  14. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/endpoints/sensors.py +176 -0
  15. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/errors.py +251 -0
  16. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/helpers/__init__.py +1 -0
  17. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/helpers/model.py +14 -0
  18. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/helpers/validator/__init__.py +18 -0
  19. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/helpers/validator/sensors.py +78 -0
  20. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/models/__init__.py +1 -0
  21. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/models/keys.py +50 -0
  22. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/models/organizations.py +23 -0
  23. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/models/sensors.py +388 -0
  24. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/py.typed +0 -0
  25. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/util/__init__.py +1 -0
  26. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/util/dt.py +23 -0
  27. ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/util/geo.py +126 -0
  28. ptr727_aiopurpleair-1.0.0.dev0/tests/__init__.py +1 -0
  29. ptr727_aiopurpleair-1.0.0.dev0/tests/common.py +21 -0
  30. ptr727_aiopurpleair-1.0.0.dev0/tests/conftest.py +1 -0
  31. ptr727_aiopurpleair-1.0.0.dev0/tests/endpoints/__init__.py +1 -0
  32. ptr727_aiopurpleair-1.0.0.dev0/tests/endpoints/test_organizations.py +76 -0
  33. ptr727_aiopurpleair-1.0.0.dev0/tests/endpoints/test_sensors.py +352 -0
  34. ptr727_aiopurpleair-1.0.0.dev0/tests/fixtures/.gitignore +0 -0
  35. ptr727_aiopurpleair-1.0.0.dev0/tests/fixtures/error_invalid_api_key_response.json +6 -0
  36. ptr727_aiopurpleair-1.0.0.dev0/tests/fixtures/error_missing_api_key_response.json +6 -0
  37. ptr727_aiopurpleair-1.0.0.dev0/tests/fixtures/error_not_found_response.json +6 -0
  38. ptr727_aiopurpleair-1.0.0.dev0/tests/fixtures/error_unknown_response.json +6 -0
  39. ptr727_aiopurpleair-1.0.0.dev0/tests/fixtures/get_keys_response.json +5 -0
  40. ptr727_aiopurpleair-1.0.0.dev0/tests/fixtures/get_organization_response.json +8 -0
  41. ptr727_aiopurpleair-1.0.0.dev0/tests/fixtures/get_sensor_response.json +128 -0
  42. ptr727_aiopurpleair-1.0.0.dev0/tests/fixtures/get_sensors_response.json +16 -0
  43. ptr727_aiopurpleair-1.0.0.dev0/tests/helpers/__init__.py +0 -0
  44. ptr727_aiopurpleair-1.0.0.dev0/tests/helpers/test_validator.py +19 -0
  45. ptr727_aiopurpleair-1.0.0.dev0/tests/models/__init__.py +1 -0
  46. ptr727_aiopurpleair-1.0.0.dev0/tests/models/__snapshots__/test_snapshots.ambr +690 -0
  47. ptr727_aiopurpleair-1.0.0.dev0/tests/models/conftest.py +48 -0
  48. ptr727_aiopurpleair-1.0.0.dev0/tests/models/test_keys.py +48 -0
  49. ptr727_aiopurpleair-1.0.0.dev0/tests/models/test_sensors.py +233 -0
  50. ptr727_aiopurpleair-1.0.0.dev0/tests/models/test_snapshots.py +34 -0
  51. ptr727_aiopurpleair-1.0.0.dev0/tests/test_api.py +315 -0
  52. ptr727_aiopurpleair-1.0.0.dev0/tests/test_live_api.py +105 -0
  53. ptr727_aiopurpleair-1.0.0.dev0/tests/util/__init__.py +1 -0
  54. ptr727_aiopurpleair-1.0.0.dev0/tests/util/test_dt.py +11 -0
  55. ptr727_aiopurpleair-1.0.0.dev0/tests/util/test_geo.py +116 -0
@@ -0,0 +1,34 @@
1
+ # Byte-compiled / cached Python
2
+ __pycache__/
3
+ *.py[cod]
4
+
5
+ # Distribution / packaging
6
+ /build/
7
+ /dist/
8
+ *.egg-info/
9
+
10
+ # Virtual environments
11
+ .venv/
12
+
13
+ # Test / coverage artifacts
14
+ .pytest_cache/
15
+ .coverage
16
+ .coverage.*
17
+ coverage.xml
18
+ coverage.json
19
+ htmlcov/
20
+
21
+ # Tooling caches
22
+ .ruff_cache/
23
+ .mypy_cache/
24
+ .pyright/
25
+
26
+ # uv keeps its cache outside the tree; uv.lock is committed. Nothing else to ignore here.
27
+
28
+ # Live-test credentials - real API keys, never committed (.env.test.example is the tracked template)
29
+ .env
30
+ .env.test
31
+
32
+ # Editor / agent
33
+ .claude
34
+ .codex
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Aaron Bach
4
+ Copyright (c) 2026 Pieter Viljoen
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,23 @@
1
+ aiopurpleair
2
+ Copyright 2026 Pieter Viljoen
3
+
4
+ This product includes software originally developed by Aaron Bach (@bachya) as
5
+ the `aiopurpleair` library (https://github.com/bachya/aiopurpleair), licensed
6
+ under the MIT License, Copyright (c) 2024 Aaron Bach.
7
+
8
+ This repository is an independent continuation of that library. It began as the
9
+ `feat/organization-endpoint-and-error-codes` branch of a fork, adding a
10
+ `GET /v1/organization` endpoint and a typed exception hierarchy mapped from the
11
+ PurpleAir API's documented error codes. Two upstream contributions related to
12
+ this work were abandoned after the upstream maintainer became unresponsive:
13
+
14
+ - The additions above, proposed against bachya/aiopurpleair, were not merged;
15
+ they are now maintained here and published to PyPI as `aiopurpleair-ptr727`.
16
+ - The companion Home Assistant integration was proposed as
17
+ home-assistant/core#140901 and is maintained separately as the
18
+ `homeassistant-purpleair` HACS custom integration.
19
+
20
+ The original MIT copyright notice is retained in LICENSE alongside the current
21
+ maintainer's. The import package name remains `aiopurpleair`; the distribution
22
+ name `aiopurpleair-ptr727` keeps it distinct from the canonical `aiopurpleair`
23
+ distribution on PyPI.
@@ -0,0 +1,346 @@
1
+ Metadata-Version: 2.4
2
+ Name: ptr727-aiopurpleair
3
+ Version: 1.0.0.dev0
4
+ Summary: Async Python client for the PurpleAir API, with the organization endpoint and typed error codes.
5
+ Project-URL: Homepage, https://github.com/ptr727/aiopurpleair
6
+ Project-URL: Source, https://github.com/ptr727/aiopurpleair
7
+ Project-URL: Issues, https://github.com/ptr727/aiopurpleair/issues
8
+ Author: Pieter Viljoen
9
+ License: MIT
10
+ License-File: LICENSE
11
+ License-File: NOTICE
12
+ Keywords: aiohttp,air-quality,asyncio,purpleair,pydantic
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Framework :: AsyncIO
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Programming Language :: Python :: Implementation :: CPython
23
+ Classifier: Topic :: Home Automation
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.13
27
+ Requires-Dist: aiohttp>=3.11.0
28
+ Requires-Dist: certifi>=2023.07.22
29
+ Requires-Dist: pydantic<3.0.0,>=2.0.0
30
+ Requires-Dist: yarl>=1.9.2
31
+ Description-Content-Type: text/markdown
32
+
33
+ # aiopurpleair
34
+
35
+ Async Python client library for the PurpleAir air-quality API, with the organization endpoint and typed error codes.
36
+
37
+ ## Build and Distribution
38
+
39
+ - **Source Code**: [GitHub][github-link] - Source code, issues, discussions, and CI/CD pipelines.
40
+ - **Versioned Releases**: [GitHub Releases][releases-link] - Version tagged source code and build artifacts.
41
+ - **PyPI Packages**: [PyPI][pypi-link] - Python wheel + sdist published to PyPI.org as `ptr727-aiopurpleair`.
42
+
43
+ ### Build Status
44
+
45
+ [![Releases Build][releasebuildstatus-shield]][actions-link]\
46
+ [![Last Commit][lastcommit-shield]][commits-link]
47
+
48
+ ### Releases
49
+
50
+ [![GitHub Release][releaseversion-shield]][releases-link]\
51
+ [![GitHub Pre-Release][prereleaseversion-shield]][releases-link]\
52
+ [![PyPI Release][pypireleaseversion-shield]][pypi-link]
53
+
54
+ ### Release Notes
55
+
56
+ See [`HISTORY.md`](./HISTORY.md) for the release notes ledger.
57
+
58
+ ## Getting Started
59
+
60
+ Get started with aiopurpleair in two easy steps:
61
+
62
+ 1. **Add aiopurpleair to your project**:
63
+
64
+ ```shell
65
+ # Add the package to your project (import name stays `aiopurpleair`)
66
+ pip install ptr727-aiopurpleair
67
+ ```
68
+
69
+ 2. **Write some code**:
70
+
71
+ ```python
72
+ import asyncio
73
+
74
+ from aiopurpleair import API
75
+
76
+
77
+ async def main() -> None:
78
+ """Check an API key and fetch sensors."""
79
+ api = API("<API_KEY>")
80
+ keys = await api.async_check_api_key()
81
+ sensors = await api.sensors.async_get_sensors(["name", "pm2.5"])
82
+ organization = await api.organizations.async_get_organization()
83
+
84
+
85
+ asyncio.run(main())
86
+ ```
87
+
88
+ See [Usage](#usage) for detailed usage instructions.
89
+
90
+ ## Table of Contents
91
+
92
+ - [aiopurpleair](#aiopurpleair)
93
+ - [Build and Distribution](#build-and-distribution)
94
+ - [Build Status](#build-status)
95
+ - [Releases](#releases)
96
+ - [Release Notes](#release-notes)
97
+ - [Getting Started](#getting-started)
98
+ - [Table of Contents](#table-of-contents)
99
+ - [Features](#features)
100
+ - [Usage](#usage)
101
+ - [Checking an API Key](#checking-an-api-key)
102
+ - [Getting Sensors](#getting-sensors)
103
+ - [Getting the Organization](#getting-the-organization)
104
+ - [Error Handling](#error-handling)
105
+ - [Connection Pooling](#connection-pooling)
106
+ - [Installation](#installation)
107
+ - [Questions or Issues](#questions-or-issues)
108
+ - [Build Artifacts](#build-artifacts)
109
+ - [API Reference](#api-reference)
110
+ - [Contributing](#contributing)
111
+ - [Origin](#origin)
112
+ - [License](#license)
113
+
114
+ ## Features
115
+
116
+ - Async client for the PurpleAir API, covering the sensors and keys endpoints.
117
+ - `GET /v1/organization` endpoint exposing remaining API points and consumption rate.
118
+ - Typed exception hierarchy mapped from the API's documented error codes.
119
+ - Timezone-aware UTC datetimes and typed Pydantic response models with a `py.typed` marker.
120
+ - Modern packaging: hatchling, uv, automatic versioning, OIDC-published releases, and 100% test coverage.
121
+
122
+ ## Usage
123
+
124
+ In-depth documentation on the API is available from [PurpleAir][purpleairapi-link]. Unless otherwise noted, aiopurpleair follows the API as closely as possible.
125
+
126
+ ### Checking an API Key
127
+
128
+ ```python
129
+ import asyncio
130
+
131
+ from aiopurpleair import API
132
+
133
+
134
+ async def main() -> None:
135
+ """Check whether an API key is valid and what properties it has."""
136
+ api = API("<API_KEY>")
137
+ response = await api.async_check_api_key()
138
+ # >>> response.api_key_type == ApiKeyType.READ
139
+ # >>> response.api_version == "V1.0.11-0.0.41"
140
+
141
+
142
+ asyncio.run(main())
143
+ ```
144
+
145
+ ### Getting Sensors
146
+
147
+ ```python
148
+ import asyncio
149
+
150
+ from aiopurpleair import API
151
+
152
+
153
+ async def main() -> None:
154
+ """Fetch sensor data for the requested fields."""
155
+ api = API("<API_KEY>")
156
+ response = await api.sensors.async_get_sensors(["name", "pm2.5"])
157
+ # >>> response.data == {131075: SensorModel(...), 131079: SensorModel(...)}
158
+
159
+
160
+ asyncio.run(main())
161
+ ```
162
+
163
+ Private sensors require their per-sensor read key: pass `read_key=` to `async_get_sensor`, or `read_keys=[...]` to `async_get_sensors`. Use `async_get_nearby_sensors(fields, latitude, longitude, distance)` for a distance-sorted search, and `get_map_url(sensor_index)` for a map link.
164
+
165
+ ### Getting the Organization
166
+
167
+ The organization endpoint reports the account's remaining API points and consumption rate, useful for surfacing a low-points warning before queries start failing:
168
+
169
+ ```python
170
+ import asyncio
171
+
172
+ from aiopurpleair import API
173
+
174
+
175
+ async def main() -> None:
176
+ """Fetch the organization associated with the API key."""
177
+ api = API("<API_KEY>")
178
+ response = await api.organizations.async_get_organization()
179
+ # >>> response.remaining_points == 500000
180
+ # >>> response.consumption_rate == 1234.5
181
+ # >>> response.organization_id == "..."
182
+ # >>> response.organization_name == "..."
183
+
184
+
185
+ asyncio.run(main())
186
+ ```
187
+
188
+ ### Error Handling
189
+
190
+ Each documented PurpleAir API error code maps to a specific exception subclass, so callers can catch a precise condition instead of pattern-matching on `str(err)`. Every subclass derives from `PurpleAirError`:
191
+
192
+ ```python
193
+ import asyncio
194
+
195
+ from aiopurpleair import API
196
+ from aiopurpleair.errors import InvalidApiKeyError, RateLimitExceededError
197
+
198
+
199
+ async def main() -> None:
200
+ """Handle specific PurpleAir error conditions."""
201
+ api = API("<API_KEY>")
202
+ try:
203
+ await api.sensors.async_get_sensors(["name"])
204
+ except InvalidApiKeyError:
205
+ ... # the API key is missing or invalid
206
+ except RateLimitExceededError:
207
+ ... # back off and retry later
208
+
209
+
210
+ asyncio.run(main())
211
+ ```
212
+
213
+ All error codes and semantics are verified against the official [PurpleAir API documentation][purpleairapi-link].
214
+
215
+ ### Connection Pooling
216
+
217
+ By default a new connection is created per coroutine. Pass an existing [`aiohttp`][aiohttp-link] `ClientSession` for connection pooling:
218
+
219
+ ```python
220
+ import asyncio
221
+
222
+ from aiohttp import ClientSession
223
+
224
+ from aiopurpleair import API
225
+
226
+
227
+ async def main() -> None:
228
+ """Reuse a session across calls."""
229
+ async with ClientSession() as session:
230
+ api = API("<API_KEY>", session=session)
231
+ ...
232
+
233
+
234
+ asyncio.run(main())
235
+ ```
236
+
237
+ ## Installation
238
+
239
+ **Project integration**:
240
+
241
+ ```shell
242
+ # Add the package to your project
243
+ pip install ptr727-aiopurpleair
244
+ ```
245
+
246
+ ```python
247
+ # Import the library (the import name stays `aiopurpleair`)
248
+ import aiopurpleair
249
+ ```
250
+
251
+ The distribution name is `ptr727-aiopurpleair` (distinct from the canonical `aiopurpleair` on PyPI); the import path is unchanged. aiopurpleair supports Python 3.13 and 3.14, and depends on `aiohttp`, `pydantic`, `yarl`, and `certifi`.
252
+
253
+ ## Questions or Issues
254
+
255
+ **General questions**:
256
+
257
+ - Use the [Discussions][discussions-link] forum for general questions.
258
+
259
+ **Bug reports**:
260
+
261
+ - Ask in the [Discussions][discussions-link] forum if you are not sure if it is a bug.
262
+ - Check the existing [Issues][issues-link] tracker for known problems.
263
+ - If the issue is unique and a bug, file it in [Issues][issues-link], and include all pertinent steps to reproduce the issue.
264
+
265
+ ## Build Artifacts
266
+
267
+ **Build process and artifacts**:
268
+
269
+ - **Package**: a Python wheel + sdist (`ptr727-aiopurpleair`), built with the [hatchling][hatchling-link] backend on a src-layout ([`src/aiopurpleair/`](./src/aiopurpleair/)) and managed with [uv][uv-link].
270
+ - **Versioning**: automatic via [Nerdbank.GitVersioning][nbgv-link] from [`version.json`](./version.json) (`1.0` base) plus git height; `main` builds a clean stable `X.Y.Z`, `develop` a `X.Y.Z.dev0` prerelease. There is no manual tagging.
271
+ - **Publishing**: releases publish to PyPI over OIDC [Trusted Publishing][trustedpublishing-link] (no stored API token). A shipped-path push to `main` (stable) or `develop` (prerelease), or a manual dispatch, cuts a [GitHub Release][releases-link] and uploads the wheel + sdist to PyPI. See [`WORKFLOW.md`](./WORKFLOW.md) for the complete CI/CD contract.
272
+
273
+ ## API Reference
274
+
275
+ PurpleAir does not publish an OpenAPI/Swagger spec. This repo reconstructs one at [`docs/purpleair-openapi.yaml`](./docs/purpleair-openapi.yaml) from PurpleAir's [apiDoc][apidoc-link]-generated docs (which serve machine-readable `api_data.js`), using [`scripts/generate_openapi.py`](./scripts/generate_openapi.py). The library's endpoint, field, and error-code coverage is validated against this spec.
276
+
277
+ Regenerate it after an upstream API change:
278
+
279
+ ```shell
280
+ # Live-fetch https://api.purpleair.com/api_data.js, rebuild and validate the spec
281
+ uv run --with pyyaml --with openapi-spec-validator python scripts/generate_openapi.py
282
+ ```
283
+
284
+ The generator takes the API version from the docs' changelog (the apiDoc build-metadata version lags behind), validates the result, and writes `docs/purpleair-openapi.yaml`. A non-empty diff means the upstream API changed. See [`AGENTS.md`](./AGENTS.md) for how the code is validated against the spec.
285
+
286
+ ## Contributing
287
+
288
+ - **Branching workflow**:
289
+ - The repo uses a two-branch model with ruleset-enforced merge methods.
290
+ - Feature branch -> `develop` via **squash merge** (develop is kept linear).
291
+ - `develop` -> `main` via **merge commit** (preserves develop's commit list on main as the second parent of each release commit).
292
+ - Dependabot targets `main` and `develop` in parallel via separate PRs and auto-merges every tier once the required check passes.
293
+ - See [`WORKFLOW.md`](./WORKFLOW.md) and [`AGENTS.md`](./AGENTS.md) for complete details.
294
+ - **Code style**:
295
+ - See [`CODESTYLE.md`](./CODESTYLE.md) and [`.editorconfig`](./.editorconfig) for Python code style rules. Everything runs through `uv run` (`ruff`, `mypy`, `pyright`, `pytest` with 100% coverage and syrupy snapshots).
296
+ - **Repository setup**:
297
+ - See [`repo-config/README.md`](./repo-config/README.md) for repo configuration details.
298
+
299
+ ## Origin
300
+
301
+ aiopurpleair is an independent, MIT-licensed continuation of the [bachya/aiopurpleair][upstream-link] PurpleAir API client. Its distinguishing capabilities originated in two upstream contributions that were abandoned after the upstream maintainers became unresponsive:
302
+
303
+ - A pull request against [bachya/aiopurpleair][upstream-link] adding the organization endpoint and typed error codes, which was not merged.
304
+ - A PurpleAir integration proposed for Home Assistant core as [home-assistant/core#140901][hacorepr-link], now maintained separately as the [`homeassistant-purpleair`][hapurpleair-link] HACS custom integration, this library's primary consumer.
305
+
306
+ The import package name is `aiopurpleair`; the distribution name `ptr727-aiopurpleair` keeps it distinct from the canonical `aiopurpleair` on PyPI. The original MIT copyright is retained alongside the current maintainer's in [LICENSE](./LICENSE) and [NOTICE](./NOTICE).
307
+
308
+ ## License
309
+
310
+ Licensed under the [MIT License][license-link]\
311
+ ![GitHub License][license-shield]
312
+
313
+ - Original `aiopurpleair` author: Aaron Bach ([@bachya][bachya-link]).
314
+ - Current maintainer: Pieter Viljoen ([@ptr727][ptr727-link]).
315
+
316
+ <!-- Shields links -->
317
+
318
+ [actions-link]: https://github.com/ptr727/aiopurpleair/actions
319
+ [commits-link]: https://github.com/ptr727/aiopurpleair/commits/main
320
+ [discussions-link]: https://github.com/ptr727/aiopurpleair/discussions
321
+ [github-link]: https://github.com/ptr727/aiopurpleair
322
+ [issues-link]: https://github.com/ptr727/aiopurpleair/issues
323
+ [lastcommit-shield]: https://img.shields.io/github/last-commit/ptr727/aiopurpleair?logo=github&label=Last%20Commit
324
+ [license-link]: ./LICENSE
325
+ [license-shield]: https://img.shields.io/github/license/ptr727/aiopurpleair?label=License
326
+ [prereleaseversion-shield]: https://img.shields.io/github/v/release/ptr727/aiopurpleair?include_prereleases&filter=*-g*&label=GitHub%20Pre-Release&logo=github
327
+ [pypi-link]: https://pypi.org/project/ptr727-aiopurpleair/
328
+ [pypireleaseversion-shield]: https://img.shields.io/pypi/v/ptr727-aiopurpleair?logo=pypi&label=PyPI%20Release
329
+ [releasebuildstatus-shield]: https://img.shields.io/github/actions/workflow/status/ptr727/aiopurpleair/publish-release.yml?logo=github&label=Releases%20Build
330
+ [releases-link]: https://github.com/ptr727/aiopurpleair/releases
331
+ [releaseversion-shield]: https://img.shields.io/github/v/release/ptr727/aiopurpleair?logo=github&label=GitHub%20Release
332
+
333
+ <!-- Other links -->
334
+
335
+ [aiohttp-link]: https://github.com/aio-libs/aiohttp
336
+ [apidoc-link]: https://apidocjs.com/
337
+ [bachya-link]: https://github.com/bachya
338
+ [hacorepr-link]: https://github.com/home-assistant/core/pull/140901
339
+ [hapurpleair-link]: https://github.com/ptr727/homeassistant-purpleair
340
+ [hatchling-link]: https://hatch.pypa.io/latest/
341
+ [nbgv-link]: https://github.com/dotnet/Nerdbank.GitVersioning
342
+ [ptr727-link]: https://github.com/ptr727
343
+ [purpleairapi-link]: https://api.purpleair.com/#api-welcome
344
+ [trustedpublishing-link]: https://docs.pypi.org/trusted-publishers/
345
+ [upstream-link]: https://github.com/bachya/aiopurpleair
346
+ [uv-link]: https://github.com/astral-sh/uv