Add Development
526
Development.md
Normal file
526
Development.md
Normal file
@@ -0,0 +1,526 @@
|
||||
# 🛠️ Development Guide
|
||||
|
||||
Set up a development environment for AdGuard Control Hub and learn how to contribute to the project.
|
||||
|
||||
## 🚀 Development Environment Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Python 3.11+** (Home Assistant requirement)
|
||||
- **Git** for version control
|
||||
- **Home Assistant Core** (development installation)
|
||||
- **AdGuard Home** instance for testing
|
||||
- **VS Code** (recommended) with Python extension
|
||||
|
||||
### 1. Clone the Repository
|
||||
|
||||
```bash
|
||||
# Clone your fork or the main repository
|
||||
git clone https://your-gitea-domain.com/your-username/adguard-control-hub.git
|
||||
cd adguard-control-hub
|
||||
|
||||
# Set up remote for upstream (if fork)
|
||||
git remote add upstream https://your-gitea-domain.com/original-user/adguard-control-hub.git
|
||||
```
|
||||
|
||||
### 2. Set Up Python Environment
|
||||
|
||||
```bash
|
||||
# Create virtual environment
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate # Linux/Mac
|
||||
# or
|
||||
venv\Scripts\activate # Windows
|
||||
|
||||
# Upgrade pip
|
||||
python -m pip install --upgrade pip
|
||||
|
||||
# Install development dependencies
|
||||
pip install -r requirements-dev.txt
|
||||
```
|
||||
|
||||
### 3. Install Home Assistant Core
|
||||
|
||||
```bash
|
||||
# Install Home Assistant for development
|
||||
pip install homeassistant
|
||||
|
||||
# Or use development version
|
||||
git clone https://github.com/home-assistant/core.git
|
||||
cd core
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
### 4. Set Up Test AdGuard Home
|
||||
|
||||
```bash
|
||||
# Download and run AdGuard Home for testing
|
||||
wget https://github.com/AdguardTeam/AdGuardHome/releases/latest/download/AdGuardHome_linux_amd64.tar.gz
|
||||
tar -xzf AdGuardHome_linux_amd64.tar.gz
|
||||
cd AdGuardHome
|
||||
|
||||
# Initial setup
|
||||
./AdGuardHome -s install
|
||||
./AdGuardHome -s start
|
||||
|
||||
# Access web interface at http://localhost:3000
|
||||
# Set up admin user: admin/development
|
||||
```
|
||||
|
||||
## 🔧 Development Workflow
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
adguard-control-hub/
|
||||
├── custom_components/adguard_hub/ # Main integration code
|
||||
│ ├── __init__.py # Integration setup
|
||||
│ ├── api.py # API wrapper
|
||||
│ ├── config_flow.py # Configuration flow
|
||||
│ ├── const.py # Constants
|
||||
│ ├── manifest.json # Integration metadata
|
||||
│ ├── services.py # Custom services
|
||||
│ ├── strings.json # UI strings
|
||||
│ └── switch.py # Switch platform
|
||||
├── tests/ # Test suite
|
||||
│ ├── __init__.py
|
||||
│ ├── conftest.py # Test configuration
|
||||
│ ├── test_api.py # API tests
|
||||
│ ├── test_config_flow.py # Config flow tests
|
||||
│ └── test_switch.py # Switch tests
|
||||
├── .gitea/workflows/ # CI/CD pipelines
|
||||
├── docs/ # Documentation
|
||||
├── requirements-dev.txt # Development dependencies
|
||||
├── pyproject.toml # Tool configuration
|
||||
├── .flake8 # Linting configuration
|
||||
└── README.md # Project documentation
|
||||
```
|
||||
|
||||
### Development Installation
|
||||
|
||||
```bash
|
||||
# Link integration to Home Assistant config
|
||||
mkdir -p ~/.homeassistant/custom_components
|
||||
ln -s $(pwd)/custom_components/adguard_hub ~/.homeassistant/custom_components/
|
||||
|
||||
# Or copy files during development
|
||||
cp -r custom_components/adguard_hub ~/.homeassistant/custom_components/
|
||||
```
|
||||
|
||||
### Running Home Assistant
|
||||
|
||||
```bash
|
||||
# Start Home Assistant in development mode
|
||||
hass --open-ui --verbose
|
||||
|
||||
# Or with specific config directory
|
||||
hass -c ~/.homeassistant --open-ui --verbose
|
||||
```
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
pytest
|
||||
|
||||
# Run specific test file
|
||||
pytest tests/test_api.py
|
||||
|
||||
# Run with coverage
|
||||
pytest --cov=custom_components.adguard_hub --cov-report=html
|
||||
|
||||
# Run integration tests only
|
||||
pytest tests/test_integration.py -v
|
||||
```
|
||||
|
||||
### Test Configuration
|
||||
|
||||
```python
|
||||
# tests/conftest.py
|
||||
import pytest
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
@pytest.fixture
|
||||
async def hass(event_loop):
|
||||
"""Create Home Assistant instance for testing."""
|
||||
hass = HomeAssistant()
|
||||
await async_setup_component(hass, "homeassistant", {})
|
||||
yield hass
|
||||
await hass.async_stop()
|
||||
|
||||
@pytest.fixture
|
||||
def mock_adguard_api():
|
||||
"""Mock AdGuard Home API for testing."""
|
||||
with patch("custom_components.adguard_hub.api.AdGuardHomeAPI") as mock:
|
||||
mock.return_value.test_connection.return_value = True
|
||||
mock.return_value.get_status.return_value = {"protection_enabled": True}
|
||||
yield mock.return_value
|
||||
```
|
||||
|
||||
### Writing Tests
|
||||
|
||||
```python
|
||||
# Example test
|
||||
async def test_protection_switch_toggle(hass, mock_adguard_api):
|
||||
"""Test protection switch can be toggled."""
|
||||
# Set up integration
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={"host": "localhost", "port": 3000, "username": "admin", "password": "test"}
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
# Load integration
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Test switch toggle
|
||||
await hass.services.async_call(
|
||||
"switch", "turn_off", {"entity_id": "switch.adguard_protection"}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify API was called
|
||||
mock_adguard_api.set_protection.assert_called_with(False)
|
||||
```
|
||||
|
||||
## 🔍 Code Quality Tools
|
||||
|
||||
### Pre-commit Setup
|
||||
|
||||
```bash
|
||||
# Install pre-commit
|
||||
pip install pre-commit
|
||||
|
||||
# Set up git hooks
|
||||
pre-commit install
|
||||
|
||||
# Run manually
|
||||
pre-commit run --all-files
|
||||
```
|
||||
|
||||
### Code Formatting
|
||||
|
||||
```bash
|
||||
# Format code with Black
|
||||
black custom_components/ tests/
|
||||
|
||||
# Sort imports with isort
|
||||
isort custom_components/ tests/
|
||||
|
||||
# Check formatting
|
||||
black --check custom_components/ tests/
|
||||
isort --check-only custom_components/ tests/
|
||||
```
|
||||
|
||||
### Linting
|
||||
|
||||
```bash
|
||||
# Lint with flake8
|
||||
flake8 custom_components/ tests/
|
||||
|
||||
# Type checking with mypy
|
||||
mypy custom_components/
|
||||
|
||||
# Security scanning with bandit
|
||||
bandit -r custom_components/
|
||||
```
|
||||
|
||||
### Configuration Files
|
||||
|
||||
#### pyproject.toml
|
||||
```toml
|
||||
[tool.black]
|
||||
line-length = 127
|
||||
target-version = ['py311']
|
||||
include = '\.pyi?$'
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
line_length = 127
|
||||
multi_line_output = 3
|
||||
include_trailing_comma = true
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.11"
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
disallow_untyped_defs = true
|
||||
ignore_missing_imports = true
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
python_files = ["test_*.py"]
|
||||
addopts = "-v --tb=short"
|
||||
```
|
||||
|
||||
#### .flake8
|
||||
```ini
|
||||
[flake8]
|
||||
max-line-length = 127
|
||||
exclude = .git,__pycache__,.venv,venv,.pytest_cache
|
||||
ignore = E203,W503,E501
|
||||
```
|
||||
|
||||
## 🔄 Continuous Integration
|
||||
|
||||
### Gitea Actions Workflow
|
||||
|
||||
The project uses Gitea Actions for CI/CD. Key workflows:
|
||||
|
||||
1. **quality-check.yml** - Code quality and security
|
||||
2. **integration-test.yml** - Integration testing
|
||||
3. **release.yml** - Automated releases
|
||||
|
||||
### Local CI Testing
|
||||
|
||||
```bash
|
||||
# Run quality checks locally
|
||||
black --check .
|
||||
isort --check-only .
|
||||
flake8 .
|
||||
mypy custom_components/
|
||||
bandit -r custom_components/
|
||||
safety check
|
||||
|
||||
# Run tests
|
||||
pytest --cov=custom_components.adguard_hub
|
||||
```
|
||||
|
||||
## 🐛 Debugging
|
||||
|
||||
### VS Code Configuration
|
||||
|
||||
Create `.vscode/launch.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Home Assistant",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "/path/to/venv/bin/hass",
|
||||
"args": [
|
||||
"-c",
|
||||
"/home/user/.homeassistant",
|
||||
"--debug",
|
||||
"--verbose"
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Debug Logging
|
||||
|
||||
```python
|
||||
# In your code
|
||||
import logging
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Debug statements
|
||||
_LOGGER.debug("Debugging info: %s", variable)
|
||||
_LOGGER.info("General info: %s", status)
|
||||
_LOGGER.warning("Warning: %s", issue)
|
||||
_LOGGER.error("Error occurred: %s", error)
|
||||
```
|
||||
|
||||
### Home Assistant Debug Mode
|
||||
|
||||
```yaml
|
||||
# configuration.yaml
|
||||
logger:
|
||||
default: info
|
||||
logs:
|
||||
custom_components.adguard_hub: debug
|
||||
custom_components.adguard_hub.api: debug
|
||||
|
||||
# Enable Home Assistant debug mode
|
||||
homeassistant:
|
||||
debug: true
|
||||
```
|
||||
|
||||
## 📝 Documentation
|
||||
|
||||
### Code Documentation
|
||||
|
||||
```python
|
||||
class AdGuardHomeAPI:
|
||||
"""API wrapper for AdGuard Home.
|
||||
|
||||
Provides methods to interact with AdGuard Home's REST API,
|
||||
including client management and service blocking.
|
||||
|
||||
Args:
|
||||
host: AdGuard Home hostname or IP address
|
||||
port: AdGuard Home web interface port
|
||||
username: Admin username for authentication
|
||||
password: Admin password for authentication
|
||||
ssl: Whether to use HTTPS
|
||||
session: aiohttp session for HTTP requests
|
||||
|
||||
Example:
|
||||
>>> api = AdGuardHomeAPI("192.168.1.100", 3000, "admin", "password")
|
||||
>>> await api.test_connection()
|
||||
True
|
||||
"""
|
||||
|
||||
async def get_clients(self) -> dict:
|
||||
"""Get all configured clients.
|
||||
|
||||
Returns:
|
||||
Dictionary containing client list and settings.
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: If API request fails
|
||||
|
||||
Example:
|
||||
>>> clients = await api.get_clients()
|
||||
>>> print(clients["clients"][0]["name"])
|
||||
"Kids iPad"
|
||||
"""
|
||||
```
|
||||
|
||||
### Wiki Updates
|
||||
|
||||
When adding new features:
|
||||
|
||||
1. Update relevant wiki pages
|
||||
2. Add examples to documentation
|
||||
3. Update troubleshooting guide if needed
|
||||
4. Add API documentation for new endpoints
|
||||
|
||||
## 🔧 Adding New Features
|
||||
|
||||
### Feature Development Process
|
||||
|
||||
1. **Plan**: Create issue describing the feature
|
||||
2. **Branch**: Create feature branch from main
|
||||
3. **Develop**: Implement feature with tests
|
||||
4. **Test**: Run full test suite
|
||||
5. **Document**: Update documentation
|
||||
6. **Review**: Submit pull request
|
||||
7. **Merge**: Merge after approval
|
||||
|
||||
### Adding New Service
|
||||
|
||||
```python
|
||||
# 1. Define service schema in const.py
|
||||
NEW_SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Required("client_name"): cv.string,
|
||||
vol.Required("new_parameter"): cv.string,
|
||||
})
|
||||
|
||||
# 2. Implement service in services.py
|
||||
async def new_service_handler(call):
|
||||
"""Handle new service call."""
|
||||
client_name = call.data["client_name"]
|
||||
new_parameter = call.data["new_parameter"]
|
||||
|
||||
try:
|
||||
# Implement service logic
|
||||
await api.new_api_method(client_name, new_parameter)
|
||||
except Exception as err:
|
||||
_LOGGER.error("New service failed: %s", err)
|
||||
raise HomeAssistantError(f"Service failed: {err}")
|
||||
|
||||
# 3. Register service
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
"new_service",
|
||||
new_service_handler,
|
||||
schema=NEW_SERVICE_SCHEMA,
|
||||
)
|
||||
|
||||
# 4. Add to strings.json
|
||||
{
|
||||
"services": {
|
||||
"new_service": {
|
||||
"name": "New Service",
|
||||
"description": "Description of new service",
|
||||
"fields": {
|
||||
"client_name": {
|
||||
"name": "Client Name",
|
||||
"description": "Name of the client"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 5. Write tests
|
||||
async def test_new_service(hass, mock_api):
|
||||
"""Test new service functionality."""
|
||||
# Test implementation
|
||||
```
|
||||
|
||||
### Adding New Platform
|
||||
|
||||
```python
|
||||
# 1. Create new platform file (e.g., sensor.py)
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up sensors."""
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"]
|
||||
|
||||
entities = []
|
||||
# Create sensor entities
|
||||
entities.append(NewSensor(coordinator))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
# 2. Add to PLATFORMS in const.py
|
||||
PLATFORMS = ["switch", "binary_sensor", "sensor", "new_platform"]
|
||||
|
||||
# 3. Update manifest.json if needed
|
||||
{
|
||||
"requirements": ["aiohttp>=3.8.0", "new_dependency>=1.0.0"]
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 Release Process
|
||||
|
||||
### Version Management
|
||||
|
||||
```bash
|
||||
# Update version in manifest.json
|
||||
{
|
||||
"version": "1.1.0"
|
||||
}
|
||||
|
||||
# Update CHANGELOG.md
|
||||
## [1.1.0] - 2025-01-XX
|
||||
### Added
|
||||
- New feature description
|
||||
### Fixed
|
||||
- Bug fix description
|
||||
|
||||
# Commit changes
|
||||
git add .
|
||||
git commit -m "Bump version to 1.1.0"
|
||||
git push
|
||||
|
||||
# Create release tag
|
||||
git tag -a v1.1.0 -m "Release version 1.1.0"
|
||||
git push origin v1.1.0
|
||||
```
|
||||
|
||||
### Release Checklist
|
||||
|
||||
- [ ] All tests pass
|
||||
- [ ] Documentation updated
|
||||
- [ ] Version bumped in manifest.json
|
||||
- [ ] CHANGELOG.md updated
|
||||
- [ ] Git tag created
|
||||
- [ ] Release notes written
|
||||
- [ ] Tested with multiple Home Assistant versions
|
||||
|
||||
---
|
||||
|
||||
**Ready to contribute?** Check out the [Contributing Guide](Contributing) for submission guidelines and code standards!
|
Reference in New Issue
Block a user