Metadata-Version: 2.4 Name: uhooapi Version: 1.2.8 Summary: A python client for uHoo APIs Author-email: Joshua Monta Project-URL: Homepage, https://github.com/getuhoo/uhooapi Project-URL: Bug Tracker, https://github.com/getuhoo/uhooapi/issues Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: 3.14 Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Requires-Python: >=3.13 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: aiohttp<4.0,>=3.13.2 Provides-Extra: dev Requires-Dist: pytest>=7.0.0; extra == "dev" Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev" Requires-Dist: pytest-cov>=4.0.0; extra == "dev" Requires-Dist: pytest-aiohttp>=1.0.0; extra == "dev" Requires-Dist: pytest-mock>=3.10.0; extra == "dev" Requires-Dist: aioresponses>=0.7.0; extra == "dev" Requires-Dist: black>=23.0.0; extra == "dev" Requires-Dist: isort>=5.12.0; extra == "dev" Requires-Dist: flake8>=6.0.0; extra == "dev" Requires-Dist: mypy>=1.0.0; extra == "dev" Requires-Dist: tomli>=2.3.0; extra == "dev" Dynamic: license-file # uhooapi - Python Client for uHoo API [![PyPI version](https://img.shields.io/pypi/v/uhooapi.svg)](https://pypi.org/project/uhooapi/) [![Python versions](https://img.shields.io/pypi/pyversions/uhooapi.svg)](https://pypi.org/project/uhooapi/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Code style: Ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff) A modern, asynchronous Python client for the uHoo air quality API. This library provides an intuitive, type-safe interface to access your uHoo device data, manage devices, and retrieve real-time air quality metrics with automatic token management and comprehensive error handling. ## โœจ Features - **๐Ÿš€ Async/Await Native**: Built on `aiohttp` for high-performance, non-blocking API calls - **๐Ÿ” Automatic Token Management**: Handles authentication, token refresh, and retry logic automatically - **๐Ÿ“ Full Type Annotations**: Complete type hints for better IDE support and reliability - **๐ŸŽฏ Production Ready**: 100% test coverage with comprehensive unit and integration tests - **๐Ÿ”„ Smart Error Handling**: Custom exceptions with automatic retry for 401/403 errors - **๐Ÿ“Š Complete Sensor Coverage**: Access to all uHoo metrics (temperature, humidity, COโ‚‚, PM2.5, virus index, etc.) - **โšก Efficient Data Processing**: Automatic averaging and rounding of sensor readings ## ๐Ÿ“ฆ Installation ### From PyPI (Recommended) ```bash pip install uhooapi ``` ### Development Installation ```bash # Clone the repository git clone https://github.com/yourusername/uhooapi.git cd uhooapi # 2. Create virtual environment python -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate # 3. Install with dev dependencies pip install -e ".[dev]" # 4. Install pre-commit hooks pre-commit install # 5. Run tests to verify pytest ``` ## ๐Ÿš€ Quick Start ```python import asyncio import aiohttp from uhooapi import Client async def main(): # Create a session and client async with aiohttp.ClientSession() as session: client = Client( api_key="your_uhoo_api_key_here", # Get from uHoo dashboard websession=session, debug=True # Enable debug logging ) # Authenticate and get token await client.login() # Discover and set up your devices await client.setup_devices() # Get all devices devices = client.get_devices() print(f"๐Ÿ“ฑ Found {len(devices)} uHoo device(s)") # Get latest data for the first device if devices: first_device_serial = list(devices.keys())[0] await client.get_latest_data(first_device_serial) # Access the device data device = devices[first_device_serial] print(f"\n๐Ÿ  Device: {device.device_name}") print(f"๐Ÿ“ Location: {device.room_name}") print(f"๐ŸŒก๏ธ Temperature: {device.temperature}ยฐC") print(f"๐Ÿ’ง Humidity: {device.humidity}%") print(f"โ˜๏ธ COโ‚‚: {device.co2} ppm") print(f"๐Ÿ’จ PM2.5: {device.pm25} ยตg/mยณ") print(f"๐Ÿฆ  Virus Risk Index: {device.virus_index}") # Run the async function asyncio.run(main()) ``` ## ๐Ÿ“– Usage Examples ### ๐Ÿ”„ Continuous Monitoring ```python import asyncio from datetime import datetime from uhooapi import Client async def monitor_air_quality(api_key: str, update_interval: int = 300): """Continuously monitor air quality and log changes.""" async with aiohttp.ClientSession() as session: client = Client(api_key=api_key, websession=session) await client.login() await client.setup_devices() print("Starting air quality monitoring...") while True: for serial_number, device in client.get_devices().items(): await client.get_latest_data(serial_number) print(f"\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}]") print(f"Device: {device.device_name} ({device.room_name})") print("-" * 40) print(f"Temperature: {device.temperature:5.1f}ยฐC") print(f"Humidity: {device.humidity:5.1f}%") print(f"COโ‚‚: {device.co2:5.0f} ppm") print(f"PM2.5: {device.pm25:5.1f} ยตg/mยณ") print(f"Virus Index: {device.virus_index:5.1f}") # Add alerts for poor air quality if device.co2 > 1000: print("โš ๏ธ Warning: High COโ‚‚ levels detected!") if device.pm25 > 35: print("โš ๏ธ Warning: Elevated PM2.5 levels!") await asyncio.sleep(update_interval) ``` ### ๐Ÿ›ก๏ธ Robust Error Handling ```python from uhooapi.errors import UnauthorizedError, ForbiddenError, RequestError async def fetch_with_retry(client: Client, serial_number: str, max_retries: int = 3): """Fetch data with exponential backoff retry logic.""" for attempt in range(max_retries): try: await client.get_latest_data(serial_number) return True except UnauthorizedError as e: print(f"โŒ Authentication failed: {e}") # Re-authenticate and retry await client.login() continue except ForbiddenError as e: print(f"๐Ÿ”’ Permission denied: {e}") return False except RequestError as e: if attempt < max_retries - 1: wait_time = 2 ** attempt # Exponential backoff print(f"๐ŸŒ Request failed (attempt {attempt + 1}/{max_retries}), " f"retrying in {wait_time}s...") await asyncio.sleep(wait_time) else: print(f"๐Ÿ’ฅ Max retries exceeded: {e}") return False return False ``` ### ๐Ÿ“ˆ Multi-Device Data Aggregation ```python async def get_environmental_summary(api_key: str): """Get summary statistics across all devices.""" async with aiohttp.ClientSession() as session: client = Client(api_key=api_key, websession=session) await client.login() await client.setup_devices() devices = client.get_devices() # Fetch data for all devices concurrently tasks = [ client.get_latest_data(serial) for serial in devices.keys() ] await asyncio.gather(*tasks) # Calculate averages temps = [d.temperature for d in devices.values()] humidities = [d.humidity for d in devices.values()] co2_levels = [d.co2 for d in devices.values()] print("\n๐Ÿ“Š Environmental Summary") print("=" * 40) print(f"Total Devices: {len(devices)}") print(f"Avg Temperature: {sum(temps)/len(temps):.1f}ยฐC") print(f"Avg Humidity: {sum(humidities)/len(humidities):.1f}%") print(f"Avg COโ‚‚: {sum(co2_levels)/len(co2_levels):.0f} ppm") # Identify problem areas worst_co2 = max(devices.values(), key=lambda d: d.co2) if worst_co2.co2 > 800: print(f"\nโš ๏ธ Highest COโ‚‚ in: {worst_co2.room_name} ({worst_co2.co2} ppm)") ``` ## ๐Ÿ—๏ธ Architecture ### Client Class (uhooapi.client.Client) ```python Client( api_key: str, # Your uHoo API key websession: aiohttp.ClientSession, # aiohttp session **kwargs # Optional: debug=True for debug logging ) ``` ### Device Class (uhooapi.device.Device) ```python device.device_name # "Living Room" device.serial_number # "UHOO12345" device.mac_address # "AA:BB:CC:DD:EE:FF" device.room_name # "Living Room" device.floor_number # 1 device.temperature # 22.5ยฐC device.humidity # 45.0% device.co2 # 800 ppm device.pm25 # 12.3 ยตg/mยณ device.virus_index # 2.5 device.mold_index # 1.8 device.tvoc # 150.0 ppb # ... and 15+ more sensors ``` ## ๐Ÿšจ Error Handling The library defines custom exceptions for different error scenarios: ```python from uhooapi.errors import ( UhooError, # Base exception RequestError, # General API failures UnauthorizedError, # 401 - Invalid/expired token ForbiddenError # 403 - Insufficient permissions ) try: await client.get_latest_data("UHOO12345") except UnauthorizedError: # Automatic retry with fresh login is built-in print("Token expired, re-authenticating...") except ForbiddenError as e: print(f"Access denied: {e.message}") except RequestError as e: print(f"API request failed (status: {e.status}): {e}") except KeyError: print("Device not found. Did you call setup_devices()?") except Exception as e: print(f"Unexpected error: {e}") ``` ## ๐Ÿงช Testing The project includes a comprehensive test suite: ```bash # Run all tests pytest # Run with coverage report pytest --cov=src/uhooapi --cov-report=html # Run specific test categories pytest tests/unit/ -v # Unit tests pytest tests/integration/ -v # Integration tests # Run tests in parallel pytest -n auto ``` ## ๐Ÿ”ง Building and Publishing ```bash # Update version in pyproject.toml first! # Build distribution packages python -m build # Check build quality twine check dist/* # Upload to TestPyPI (for testing) python -m twine upload --repository-url https://test.pypi.org/legacy/ dist/* # Upload to PyPI python -m twine upload dist/* ``` ## ๐Ÿ“ Project Structure ```text uhooapi/ โ”œโ”€โ”€ src/uhooapi/ # Source code โ”‚ โ”œโ”€โ”€ __init__.py # Package exports โ”‚ โ”œโ”€โ”€ client.py # Main Client class โ”‚ โ”œโ”€โ”€ api.py # Low-level API wrapper โ”‚ โ”œโ”€โ”€ device.py # Device data model (22+ sensors) โ”‚ โ”œโ”€โ”€ errors.py # Custom exceptions โ”‚ โ”œโ”€โ”€ const.py # Constants and defaults โ”‚ โ”œโ”€โ”€ endpoints.py # API endpoint configurations โ”‚ โ””โ”€โ”€ util.py # Utility functions โ”œโ”€โ”€ tests/ # Test suite โ”‚ โ”œโ”€โ”€ unit/ # Unit tests (mocked) โ”‚ โ”‚ โ”œโ”€โ”€ test_client.py # Client tests โ”‚ โ”‚ โ”œโ”€โ”€ test_api.py # API tests โ”‚ โ”‚ โ””โ”€โ”€ test_device.py # Device model tests โ”‚ โ”œโ”€โ”€ integration/ # Integration tests โ”‚ โ””โ”€โ”€ conftest.py # Test fixtures โ”œโ”€โ”€ pyproject.toml # Package configuration โ”œโ”€โ”€ README.md # This file โ”œโ”€โ”€ pre-commit-config.yaml # Code quality hooks โ””โ”€โ”€ .github/workflows/ # CI/CD pipelines (optional) ``` ## ๐Ÿค Contributing We welcome contributions! Here's how to help: 1. Fork the repository 2. Clone your fork: git clone https://github.com/yourusername/uhooapi.git 3. Create a branch: git checkout -b feature/amazing-feature 4. Make your changes and add tests 5. Run tests: pytest && pre-commit run --all-files 6. Commit: git commit -m 'Add amazing feature' 7. Push: git push origin feature/amazing-feature 8. Open a Pull Request