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.
- ptr727_aiopurpleair-1.0.0.dev0/.gitignore +34 -0
- ptr727_aiopurpleair-1.0.0.dev0/LICENSE +22 -0
- ptr727_aiopurpleair-1.0.0.dev0/NOTICE +23 -0
- ptr727_aiopurpleair-1.0.0.dev0/PKG-INFO +346 -0
- ptr727_aiopurpleair-1.0.0.dev0/README.md +314 -0
- ptr727_aiopurpleair-1.0.0.dev0/pyproject.toml +115 -0
- ptr727_aiopurpleair-1.0.0.dev0/repo-config/README.md +64 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/__init__.py +6 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/_version.py +3 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/api.py +121 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/const.py +160 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/endpoints/__init__.py +65 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/endpoints/organizations.py +27 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/endpoints/sensors.py +176 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/errors.py +251 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/helpers/__init__.py +1 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/helpers/model.py +14 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/helpers/validator/__init__.py +18 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/helpers/validator/sensors.py +78 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/models/__init__.py +1 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/models/keys.py +50 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/models/organizations.py +23 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/models/sensors.py +388 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/py.typed +0 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/util/__init__.py +1 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/util/dt.py +23 -0
- ptr727_aiopurpleair-1.0.0.dev0/src/aiopurpleair/util/geo.py +126 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/__init__.py +1 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/common.py +21 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/conftest.py +1 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/endpoints/__init__.py +1 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/endpoints/test_organizations.py +76 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/endpoints/test_sensors.py +352 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/fixtures/.gitignore +0 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/fixtures/error_invalid_api_key_response.json +6 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/fixtures/error_missing_api_key_response.json +6 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/fixtures/error_not_found_response.json +6 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/fixtures/error_unknown_response.json +6 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/fixtures/get_keys_response.json +5 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/fixtures/get_organization_response.json +8 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/fixtures/get_sensor_response.json +128 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/fixtures/get_sensors_response.json +16 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/helpers/__init__.py +0 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/helpers/test_validator.py +19 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/models/__init__.py +1 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/models/__snapshots__/test_snapshots.ambr +690 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/models/conftest.py +48 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/models/test_keys.py +48 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/models/test_sensors.py +233 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/models/test_snapshots.py +34 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/test_api.py +315 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/test_live_api.py +105 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/util/__init__.py +1 -0
- ptr727_aiopurpleair-1.0.0.dev0/tests/util/test_dt.py +11 -0
- 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
|