feat(influxdb3): WIP: Use multi-file code and modules in plugins
parent
be87ffc5b7
commit
b75bc95494
|
|
@ -0,0 +1,696 @@
|
||||||
|
---
|
||||||
|
title: Use multi-file Python code and modules in plugins
|
||||||
|
description: |
|
||||||
|
Organize complex plugin logic across multiple Python files and modules for better code reuse, testing, and maintainability in InfluxDB 3 Processing Engine plugins.
|
||||||
|
menu:
|
||||||
|
influxdb3_core:
|
||||||
|
name: Use multi-file plugins
|
||||||
|
parent: Processing engine and Python plugins
|
||||||
|
weight: 101
|
||||||
|
influxdb3/core/tags: [processing engine, plugins, python, modules]
|
||||||
|
related:
|
||||||
|
- /influxdb3/core/plugins/
|
||||||
|
- /influxdb3/core/plugins/extend-plugin/
|
||||||
|
- /influxdb3/core/reference/cli/influxdb3/create/trigger/
|
||||||
|
---
|
||||||
|
|
||||||
|
As your plugin logic grows in complexity, organizing code across multiple Python files improves maintainability, enables code reuse, and makes testing easier.
|
||||||
|
The InfluxDB 3 Processing Engine supports multi-file plugin architectures using standard Python module patterns.
|
||||||
|
|
||||||
|
## Before you begin
|
||||||
|
|
||||||
|
Ensure you have:
|
||||||
|
|
||||||
|
- A working InfluxDB 3 Core instance with the Processing Engine enabled
|
||||||
|
- Basic understanding of [Python modules and packages](https://docs.python.org/3/tutorial/modules.html)
|
||||||
|
- Familiarity with [creating InfluxDB 3 plugins](/influxdb3/core/plugins/)
|
||||||
|
|
||||||
|
## Multi-file plugin structure
|
||||||
|
|
||||||
|
A multi-file plugin is a directory containing Python files organized as a package.
|
||||||
|
The directory must include an `__init__.py` file that serves as the entry point and contains your trigger function.
|
||||||
|
|
||||||
|
### Basic structure
|
||||||
|
|
||||||
|
```
|
||||||
|
my_plugin/
|
||||||
|
├── __init__.py # Required - entry point with trigger function
|
||||||
|
├── processors.py # Data processing functions
|
||||||
|
├── utils.py # Helper utilities
|
||||||
|
└── config.py # Configuration management
|
||||||
|
```
|
||||||
|
|
||||||
|
### Required: **init**.py entry point
|
||||||
|
|
||||||
|
The `__init__.py` file must contain the trigger function that InfluxDB calls when the trigger fires.
|
||||||
|
This file imports and orchestrates code from other modules in your plugin.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# my_plugin/__init__.py
|
||||||
|
from .processors import process_data
|
||||||
|
from .config import load_settings
|
||||||
|
from .utils import format_output
|
||||||
|
|
||||||
|
def process_writes(influxdb3_local, table_batches, args=None):
|
||||||
|
"""Entry point for WAL trigger."""
|
||||||
|
settings = load_settings(args)
|
||||||
|
|
||||||
|
for table_batch in table_batches:
|
||||||
|
processed_data = process_data(table_batch, settings)
|
||||||
|
output = format_output(processed_data)
|
||||||
|
influxdb3_local.write(output)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Organizing plugin code
|
||||||
|
|
||||||
|
### Separate concerns into modules
|
||||||
|
|
||||||
|
Organize your plugin code by functional responsibility to improve maintainability and testing.
|
||||||
|
|
||||||
|
#### processors.py - Data transformation logic
|
||||||
|
|
||||||
|
```python
|
||||||
|
# my_plugin/processors.py
|
||||||
|
"""Data processing and transformation functions."""
|
||||||
|
|
||||||
|
def process_data(table_batch, settings):
|
||||||
|
"""Transform data according to configuration settings."""
|
||||||
|
table_name = table_batch["table_name"]
|
||||||
|
rows = table_batch["rows"]
|
||||||
|
|
||||||
|
transformed_rows = []
|
||||||
|
for row in rows:
|
||||||
|
transformed = transform_row(row, settings)
|
||||||
|
if transformed:
|
||||||
|
transformed_rows.append(transformed)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"table": table_name,
|
||||||
|
"rows": transformed_rows,
|
||||||
|
"count": len(transformed_rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
def transform_row(row, settings):
|
||||||
|
"""Apply transformations to a single row."""
|
||||||
|
# Apply threshold filtering
|
||||||
|
if "value" in row and row["value"] < settings.get("min_value", 0):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Apply unit conversion if configured
|
||||||
|
if settings.get("convert_units"):
|
||||||
|
row["value"] = row["value"] * settings.get("conversion_factor", 1.0)
|
||||||
|
|
||||||
|
return row
|
||||||
|
```
|
||||||
|
|
||||||
|
#### config.py - Configuration management
|
||||||
|
|
||||||
|
```python
|
||||||
|
# my_plugin/config.py
|
||||||
|
"""Plugin configuration parsing and validation."""
|
||||||
|
|
||||||
|
DEFAULT_SETTINGS = {
|
||||||
|
"min_value": 0.0,
|
||||||
|
"convert_units": False,
|
||||||
|
"conversion_factor": 1.0,
|
||||||
|
"output_measurement": "processed_data",
|
||||||
|
}
|
||||||
|
|
||||||
|
def load_settings(args):
|
||||||
|
"""Load and validate plugin settings from trigger arguments."""
|
||||||
|
settings = DEFAULT_SETTINGS.copy()
|
||||||
|
|
||||||
|
if not args:
|
||||||
|
return settings
|
||||||
|
|
||||||
|
# Parse numeric arguments
|
||||||
|
if "min_value" in args:
|
||||||
|
settings["min_value"] = float(args["min_value"])
|
||||||
|
|
||||||
|
if "conversion_factor" in args:
|
||||||
|
settings["conversion_factor"] = float(args["conversion_factor"])
|
||||||
|
|
||||||
|
# Parse boolean arguments
|
||||||
|
if "convert_units" in args:
|
||||||
|
settings["convert_units"] = args["convert_units"].lower() in ("true", "1", "yes")
|
||||||
|
|
||||||
|
# Parse string arguments
|
||||||
|
if "output_measurement" in args:
|
||||||
|
settings["output_measurement"] = args["output_measurement"]
|
||||||
|
|
||||||
|
return settings
|
||||||
|
|
||||||
|
def validate_settings(settings):
|
||||||
|
"""Validate settings and raise exceptions for invalid configurations."""
|
||||||
|
if settings["min_value"] < 0:
|
||||||
|
raise ValueError("min_value must be non-negative")
|
||||||
|
|
||||||
|
if settings["conversion_factor"] <= 0:
|
||||||
|
raise ValueError("conversion_factor must be positive")
|
||||||
|
|
||||||
|
return True
|
||||||
|
```
|
||||||
|
|
||||||
|
#### utils.py - Helper functions
|
||||||
|
|
||||||
|
```python
|
||||||
|
# my_plugin/utils.py
|
||||||
|
"""Utility functions for data formatting and logging."""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def format_output(processed_data):
|
||||||
|
"""Format processed data for writing to InfluxDB."""
|
||||||
|
from influxdb3_local import LineBuilder
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
measurement = processed_data.get("measurement", "processed_data")
|
||||||
|
|
||||||
|
for row in processed_data["rows"]:
|
||||||
|
line = LineBuilder(measurement)
|
||||||
|
|
||||||
|
# Add tags from row
|
||||||
|
for key, value in row.items():
|
||||||
|
if key.startswith("tag_"):
|
||||||
|
line.tag(key.replace("tag_", ""), str(value))
|
||||||
|
|
||||||
|
# Add fields from row
|
||||||
|
for key, value in row.items():
|
||||||
|
if key.startswith("field_"):
|
||||||
|
field_name = key.replace("field_", "")
|
||||||
|
if isinstance(value, float):
|
||||||
|
line.float64_field(field_name, value)
|
||||||
|
elif isinstance(value, int):
|
||||||
|
line.int64_field(field_name, value)
|
||||||
|
elif isinstance(value, str):
|
||||||
|
line.string_field(field_name, value)
|
||||||
|
|
||||||
|
lines.append(line)
|
||||||
|
|
||||||
|
return lines
|
||||||
|
|
||||||
|
def log_metrics(influxdb3_local, operation, duration_ms, record_count):
|
||||||
|
"""Log plugin performance metrics."""
|
||||||
|
influxdb3_local.info(
|
||||||
|
f"Operation: {operation}, "
|
||||||
|
f"Duration: {duration_ms}ms, "
|
||||||
|
f"Records: {record_count}"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Importing external libraries
|
||||||
|
|
||||||
|
Multi-file plugins can use both relative imports (for your own modules) and absolute imports (for external libraries).
|
||||||
|
|
||||||
|
### Relative imports for plugin modules
|
||||||
|
|
||||||
|
Use relative imports to reference other modules within your plugin:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# my_plugin/__init__.py
|
||||||
|
from .processors import process_data # Same package
|
||||||
|
from .config import load_settings # Same package
|
||||||
|
from .utils import format_output # Same package
|
||||||
|
|
||||||
|
# Relative imports from subdirectories
|
||||||
|
from .transforms.aggregators import calculate_mean
|
||||||
|
from .integrations.webhook import send_notification
|
||||||
|
```
|
||||||
|
|
||||||
|
### Absolute imports for external libraries
|
||||||
|
|
||||||
|
Use absolute imports for standard library and third-party packages:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# my_plugin/processors.py
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
# Third-party libraries (must be installed with influxdb3 install package)
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
```
|
||||||
|
|
||||||
|
### Installing third-party dependencies
|
||||||
|
|
||||||
|
Before using external libraries, install them into the Processing Engine's Python environment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install packages for your plugin
|
||||||
|
influxdb3 install package pandas numpy requests
|
||||||
|
```
|
||||||
|
|
||||||
|
For Docker deployments:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec -it CONTAINER_NAME influxdb3 install package pandas numpy requests
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced plugin patterns
|
||||||
|
|
||||||
|
### Nested module structure
|
||||||
|
|
||||||
|
For complex plugins, organize code into subdirectories:
|
||||||
|
|
||||||
|
```
|
||||||
|
my_advanced_plugin/
|
||||||
|
├── __init__.py
|
||||||
|
├── config.py
|
||||||
|
├── transforms/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── aggregators.py
|
||||||
|
│ └── filters.py
|
||||||
|
├── integrations/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── webhook.py
|
||||||
|
│ └── email.py
|
||||||
|
└── utils/
|
||||||
|
├── __init__.py
|
||||||
|
├── logging.py
|
||||||
|
└── validators.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Import from nested modules:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# my_advanced_plugin/__init__.py
|
||||||
|
from .transforms.aggregators import calculate_statistics
|
||||||
|
from .transforms.filters import apply_threshold_filter
|
||||||
|
from .integrations.webhook import send_alert
|
||||||
|
from .utils.logging import setup_logger
|
||||||
|
|
||||||
|
def process_writes(influxdb3_local, table_batches, args=None):
|
||||||
|
logger = setup_logger(influxdb3_local)
|
||||||
|
|
||||||
|
for table_batch in table_batches:
|
||||||
|
# Filter data
|
||||||
|
filtered = apply_threshold_filter(table_batch, threshold=100)
|
||||||
|
|
||||||
|
# Calculate statistics
|
||||||
|
stats = calculate_statistics(filtered)
|
||||||
|
|
||||||
|
# Send alerts if needed
|
||||||
|
if stats["max"] > 1000:
|
||||||
|
send_alert(stats, logger)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Shared code across plugins
|
||||||
|
|
||||||
|
Share common code across multiple plugins using a shared module directory:
|
||||||
|
|
||||||
|
```
|
||||||
|
plugins/
|
||||||
|
├── shared/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── formatters.py
|
||||||
|
│ └── validators.py
|
||||||
|
├── plugin_a/
|
||||||
|
│ └── __init__.py
|
||||||
|
└── plugin_b/
|
||||||
|
└── __init__.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the shared directory to Python's module search path in your plugin:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# plugin_a/__init__.py
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Add shared directory to path
|
||||||
|
plugin_dir = Path(__file__).parent.parent
|
||||||
|
sys.path.insert(0, str(plugin_dir))
|
||||||
|
|
||||||
|
# Now import from shared
|
||||||
|
from shared.formatters import format_line_protocol
|
||||||
|
from shared.validators import validate_data
|
||||||
|
|
||||||
|
def process_writes(influxdb3_local, table_batches, args=None):
|
||||||
|
for table_batch in table_batches:
|
||||||
|
if validate_data(table_batch):
|
||||||
|
formatted = format_line_protocol(table_batch)
|
||||||
|
influxdb3_local.write(formatted)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing multi-file plugins
|
||||||
|
|
||||||
|
### Unit testing individual modules
|
||||||
|
|
||||||
|
Test modules independently before integration:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# tests/test_processors.py
|
||||||
|
import unittest
|
||||||
|
from my_plugin.processors import transform_row
|
||||||
|
from my_plugin.config import load_settings
|
||||||
|
|
||||||
|
class TestProcessors(unittest.TestCase):
|
||||||
|
def test_transform_row_filtering(self):
|
||||||
|
"""Test that rows below threshold are filtered."""
|
||||||
|
settings = {"min_value": 10.0}
|
||||||
|
row = {"value": 5.0}
|
||||||
|
|
||||||
|
result = transform_row(row, settings)
|
||||||
|
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_transform_row_conversion(self):
|
||||||
|
"""Test unit conversion."""
|
||||||
|
settings = {
|
||||||
|
"convert_units": True,
|
||||||
|
"conversion_factor": 2.0,
|
||||||
|
"min_value": 0.0
|
||||||
|
}
|
||||||
|
row = {"value": 10.0}
|
||||||
|
|
||||||
|
result = transform_row(row, settings)
|
||||||
|
|
||||||
|
self.assertEqual(result["value"], 20.0)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing with the influxdb3 CLI
|
||||||
|
|
||||||
|
Test your complete multi-file plugin before deployment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test scheduled plugin
|
||||||
|
influxdb3 test schedule_plugin \
|
||||||
|
--database testdb \
|
||||||
|
--schedule "0 0 * * * *" \
|
||||||
|
--plugin-dir /path/to/plugins \
|
||||||
|
my_plugin
|
||||||
|
|
||||||
|
# Test WAL plugin with sample data
|
||||||
|
influxdb3 test wal_plugin \
|
||||||
|
--database testdb \
|
||||||
|
--plugin-dir /path/to/plugins \
|
||||||
|
my_plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
For more testing options, see the [influxdb3 test reference](/influxdb3/core/reference/cli/influxdb3/test/).
|
||||||
|
|
||||||
|
## Deploying multi-file plugins
|
||||||
|
|
||||||
|
### Upload plugin directory
|
||||||
|
|
||||||
|
Upload your complete plugin directory when creating a trigger:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Upload the entire plugin directory
|
||||||
|
influxdb3 create trigger \
|
||||||
|
--trigger-spec "table:sensor_data" \
|
||||||
|
--path "/local/path/to/my_plugin" \
|
||||||
|
--upload \
|
||||||
|
--database mydb \
|
||||||
|
sensor_processor
|
||||||
|
```
|
||||||
|
|
||||||
|
The `--upload` flag transfers all files in the directory to the server's plugin directory.
|
||||||
|
|
||||||
|
### Update plugin code
|
||||||
|
|
||||||
|
Update all files in a running plugin:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Update the plugin with new code
|
||||||
|
influxdb3 update trigger \
|
||||||
|
--database mydb \
|
||||||
|
--trigger-name sensor_processor \
|
||||||
|
--path "/local/path/to/my_plugin"
|
||||||
|
```
|
||||||
|
|
||||||
|
The update replaces all plugin files while preserving trigger configuration.
|
||||||
|
|
||||||
|
## Best practices
|
||||||
|
|
||||||
|
### Code organization
|
||||||
|
|
||||||
|
- **Single responsibility**: Each module should have one clear purpose
|
||||||
|
- **Shallow hierarchies**: Avoid deeply nested directory structures (2-3 levels maximum)
|
||||||
|
- **Descriptive names**: Use clear, descriptive module and function names
|
||||||
|
- **Module size**: Keep modules under 300-400 lines for maintainability
|
||||||
|
|
||||||
|
### Import management
|
||||||
|
|
||||||
|
- **Explicit imports**: Use explicit imports rather than `from module import *`
|
||||||
|
- **Standard library first**: Import standard library, then third-party, then local modules
|
||||||
|
- **Avoid circular imports**: Design modules to prevent circular dependencies
|
||||||
|
|
||||||
|
Example import organization:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Standard library
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Third-party packages
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
# Local modules
|
||||||
|
from .config import load_settings
|
||||||
|
from .processors import process_data
|
||||||
|
from .utils import format_output
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error handling
|
||||||
|
|
||||||
|
Centralize error handling in your entry point:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# my_plugin/__init__.py
|
||||||
|
from .processors import process_data
|
||||||
|
from .config import load_settings, validate_settings
|
||||||
|
|
||||||
|
def process_writes(influxdb3_local, table_batches, args=None):
|
||||||
|
try:
|
||||||
|
# Load and validate configuration
|
||||||
|
settings = load_settings(args)
|
||||||
|
validate_settings(settings)
|
||||||
|
|
||||||
|
# Process data
|
||||||
|
for table_batch in table_batches:
|
||||||
|
process_data(influxdb3_local, table_batch, settings)
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
influxdb3_local.error(f"Configuration error: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
influxdb3_local.error(f"Unexpected error: {e}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
Document your modules with docstrings:
|
||||||
|
|
||||||
|
```python
|
||||||
|
"""
|
||||||
|
my_plugin - Data processing plugin for sensor data.
|
||||||
|
|
||||||
|
This plugin processes incoming sensor data by:
|
||||||
|
1. Filtering values below configured threshold
|
||||||
|
2. Converting units if requested
|
||||||
|
3. Writing processed data to output measurement
|
||||||
|
|
||||||
|
Modules:
|
||||||
|
- processors: Core data transformation logic
|
||||||
|
- config: Configuration parsing and validation
|
||||||
|
- utils: Helper functions for formatting and logging
|
||||||
|
"""
|
||||||
|
|
||||||
|
def process_writes(influxdb3_local, table_batches, args=None):
|
||||||
|
"""Process incoming sensor data writes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
influxdb3_local: InfluxDB API interface
|
||||||
|
table_batches: List of table batches with written data
|
||||||
|
args: Optional trigger arguments for configuration
|
||||||
|
|
||||||
|
Trigger arguments:
|
||||||
|
min_value (float): Minimum value threshold
|
||||||
|
convert_units (bool): Enable unit conversion
|
||||||
|
conversion_factor (float): Conversion multiplier
|
||||||
|
output_measurement (str): Target measurement name
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example: Complete multi-file plugin
|
||||||
|
|
||||||
|
Here's a complete example of a temperature monitoring plugin with multi-file organization:
|
||||||
|
|
||||||
|
### Plugin structure
|
||||||
|
|
||||||
|
```
|
||||||
|
temperature_monitor/
|
||||||
|
├── __init__.py
|
||||||
|
├── config.py
|
||||||
|
├── processors.py
|
||||||
|
└── alerts.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### **init**.py
|
||||||
|
|
||||||
|
```python
|
||||||
|
# temperature_monitor/__init__.py
|
||||||
|
"""Temperature monitoring plugin with alerting."""
|
||||||
|
|
||||||
|
from .config import load_config
|
||||||
|
from .processors import calculate_statistics
|
||||||
|
from .alerts import check_thresholds
|
||||||
|
|
||||||
|
def process_scheduled_call(influxdb3_local, call_time, args=None):
|
||||||
|
"""Monitor temperature data and send alerts."""
|
||||||
|
try:
|
||||||
|
config = load_config(args)
|
||||||
|
|
||||||
|
# Query recent temperature data
|
||||||
|
query = f"""
|
||||||
|
SELECT temp_value, location
|
||||||
|
FROM {config['measurement']}
|
||||||
|
WHERE time > now() - INTERVAL '{config['window']}'
|
||||||
|
"""
|
||||||
|
results = influxdb3_local.query(query)
|
||||||
|
|
||||||
|
# Calculate statistics
|
||||||
|
stats = calculate_statistics(results)
|
||||||
|
|
||||||
|
# Check thresholds and alert
|
||||||
|
check_thresholds(influxdb3_local, stats, config)
|
||||||
|
|
||||||
|
influxdb3_local.info(
|
||||||
|
f"Processed {len(results)} readings "
|
||||||
|
f"from {len(stats)} locations"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
influxdb3_local.error(f"Plugin error: {e}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### config.py
|
||||||
|
|
||||||
|
```python
|
||||||
|
# temperature_monitor/config.py
|
||||||
|
"""Configuration management for temperature monitor."""
|
||||||
|
|
||||||
|
DEFAULTS = {
|
||||||
|
"measurement": "temperature",
|
||||||
|
"window": "1 hour",
|
||||||
|
"high_threshold": 30.0,
|
||||||
|
"low_threshold": 10.0,
|
||||||
|
"alert_measurement": "temperature_alerts"
|
||||||
|
}
|
||||||
|
|
||||||
|
def load_config(args):
|
||||||
|
"""Load configuration from trigger arguments."""
|
||||||
|
config = DEFAULTS.copy()
|
||||||
|
|
||||||
|
if args:
|
||||||
|
for key in DEFAULTS:
|
||||||
|
if key in args:
|
||||||
|
if key.endswith("_threshold"):
|
||||||
|
config[key] = float(args[key])
|
||||||
|
else:
|
||||||
|
config[key] = args[key]
|
||||||
|
|
||||||
|
return config
|
||||||
|
```
|
||||||
|
|
||||||
|
### processors.py
|
||||||
|
|
||||||
|
```python
|
||||||
|
# temperature_monitor/processors.py
|
||||||
|
"""Data processing functions."""
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
def calculate_statistics(data):
|
||||||
|
"""Calculate statistics by location."""
|
||||||
|
stats = defaultdict(lambda: {
|
||||||
|
"count": 0,
|
||||||
|
"sum": 0.0,
|
||||||
|
"min": float('inf'),
|
||||||
|
"max": float('-inf')
|
||||||
|
})
|
||||||
|
|
||||||
|
for row in data:
|
||||||
|
location = row.get("location", "unknown")
|
||||||
|
value = float(row.get("temp_value", 0))
|
||||||
|
|
||||||
|
s = stats[location]
|
||||||
|
s["count"] += 1
|
||||||
|
s["sum"] += value
|
||||||
|
s["min"] = min(s["min"], value)
|
||||||
|
s["max"] = max(s["max"], value)
|
||||||
|
|
||||||
|
# Calculate averages
|
||||||
|
for location, s in stats.items():
|
||||||
|
if s["count"] > 0:
|
||||||
|
s["avg"] = s["sum"] / s["count"]
|
||||||
|
|
||||||
|
return dict(stats)
|
||||||
|
```
|
||||||
|
|
||||||
|
### alerts.py
|
||||||
|
|
||||||
|
```python
|
||||||
|
# temperature_monitor/alerts.py
|
||||||
|
"""Alert checking and notification."""
|
||||||
|
|
||||||
|
def check_thresholds(influxdb3_local, stats, config):
|
||||||
|
"""Check temperature thresholds and write alerts."""
|
||||||
|
from influxdb3_local import LineBuilder
|
||||||
|
|
||||||
|
high_threshold = config["high_threshold"]
|
||||||
|
low_threshold = config["low_threshold"]
|
||||||
|
alert_measurement = config["alert_measurement"]
|
||||||
|
|
||||||
|
for location, s in stats.items():
|
||||||
|
if s["max"] > high_threshold:
|
||||||
|
line = LineBuilder(alert_measurement)
|
||||||
|
line.tag("location", location)
|
||||||
|
line.tag("severity", "high")
|
||||||
|
line.float64_field("temperature", s["max"])
|
||||||
|
line.string_field("message",
|
||||||
|
f"High temperature: {s['max']}°C exceeds {high_threshold}°C")
|
||||||
|
|
||||||
|
influxdb3_local.write(line)
|
||||||
|
influxdb3_local.warn(f"High temperature alert for {location}")
|
||||||
|
|
||||||
|
elif s["min"] < low_threshold:
|
||||||
|
line = LineBuilder(alert_measurement)
|
||||||
|
line.tag("location", location)
|
||||||
|
line.tag("severity", "low")
|
||||||
|
line.float64_field("temperature", s["min"])
|
||||||
|
line.string_field("message",
|
||||||
|
f"Low temperature: {s['min']}°C below {low_threshold}°C")
|
||||||
|
|
||||||
|
influxdb3_local.write(line)
|
||||||
|
influxdb3_local.warn(f"Low temperature alert for {location}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deploy the plugin
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create trigger with configuration
|
||||||
|
influxdb3 create trigger \
|
||||||
|
--trigger-spec "every:5m" \
|
||||||
|
--path "/local/path/to/temperature_monitor" \
|
||||||
|
--upload \
|
||||||
|
--trigger-arguments high_threshold=35,low_threshold=5,window="15 minutes" \
|
||||||
|
--database sensors \
|
||||||
|
temp_monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
## Related resources
|
||||||
|
|
||||||
|
- [Processing engine and Python plugins](/influxdb3/core/plugins/)
|
||||||
|
- [Extend plugins with API features](/influxdb3/core/plugins/extend-plugin/)
|
||||||
|
- [Plugin library](/influxdb3/core/plugins/library/)
|
||||||
|
- [influxdb3 create trigger](/influxdb3/core/reference/cli/influxdb3/create/trigger/)
|
||||||
|
- [influxdb3 test](/influxdb3/core/reference/cli/influxdb3/test/)
|
||||||
Loading…
Reference in New Issue