138 lines
7.2 KiB
Markdown
138 lines
7.2 KiB
Markdown
# exclude_patterns Implementation Summary
|
|
|
|
## Original Questions
|
|
|
|
1. **How many different places test for exclude_patterns?**
|
|
- There are 3 distinct places in the code that test for exclude_patterns.
|
|
|
|
2. **Should a common function be written for that as well like was done for the unknown_device_patterns?**
|
|
- Yes, a common function has been implemented to centralize the exclude_patterns logic, similar to what was done for unknown_device_patterns.
|
|
|
|
## Analysis of exclude_patterns Checks
|
|
|
|
The script performed checks against `exclude_patterns` in 3 distinct places:
|
|
|
|
1. In the `is_tasmota_device` function (lines 165-185)
|
|
- Used during device discovery to determine if a device should be excluded based on its name or hostname.
|
|
|
|
2. In the `save_tasmota_config` function via a local `is_device_excluded` function (lines 423-431)
|
|
- Used when saving device information to a JSON file to determine which devices should be excluded from the current or deprecated lists.
|
|
|
|
3. In the `process_single_device` function (lines 1589-1610)
|
|
- Used when processing a single device by IP or hostname to determine if the device should be excluded.
|
|
|
|
The pattern matching logic was similar but not identical across these locations:
|
|
|
|
- All implementations converted patterns to lowercase for case-insensitive matching.
|
|
- All implementations converted glob patterns (with *) to regex patterns.
|
|
- All implementations checked if the device name or hostname matched any exclude pattern.
|
|
- However, there were differences in how special patterns like `^.*something.*` were handled.
|
|
|
|
## Implementation of Common Function
|
|
|
|
A new function called `is_device_excluded` has been added to the TasmotaManager.py file. This function:
|
|
|
|
```python
|
|
def is_device_excluded(self, device_name: str, hostname: str = '', patterns: list = None, log_level: str = 'debug') -> bool:
|
|
"""Check if a device name or hostname matches any pattern in exclude_patterns.
|
|
|
|
This function provides a centralized way to check if a device should be excluded
|
|
based on its name or hostname matching any of the exclude_patterns defined in the
|
|
configuration. It uses case-insensitive matching and supports glob patterns (with *)
|
|
in the patterns list.
|
|
|
|
Args:
|
|
device_name: The device name to check against exclude_patterns
|
|
hostname: The device hostname to check against exclude_patterns (optional)
|
|
patterns: Optional list of patterns to check against. If not provided,
|
|
patterns will be loaded from the configuration.
|
|
log_level: The logging level to use ('debug', 'info', 'warning', 'error'). Default is 'debug'.
|
|
|
|
Returns:
|
|
bool: True if the device should be excluded (name or hostname matches any pattern),
|
|
False otherwise
|
|
|
|
Examples:
|
|
# Check if a device should be excluded based on patterns in the config
|
|
if manager.is_device_excluded("homeassistant", "homeassistant.local"):
|
|
print("This device should be excluded")
|
|
|
|
# Check against a specific list of patterns
|
|
custom_patterns = ["^homeassistant*", "^.*sonos.*"]
|
|
if manager.is_device_excluded("sonos-speaker", "sonos.local", custom_patterns, log_level='info'):
|
|
print("This device matches a custom exclude pattern")
|
|
"""
|
|
```
|
|
|
|
The function includes the following features:
|
|
|
|
1. **Centralized Logic**: Provides a single place for exclude pattern matching logic.
|
|
2. **Case Insensitivity**: Performs case-insensitive matching.
|
|
3. **Glob Pattern Support**: Supports glob patterns (with *) in the patterns list.
|
|
4. **Special Pattern Handling**: Properly handles patterns that start with `^.*` to match anywhere in the string.
|
|
5. **Flexible Pattern Source**: Can use patterns from the configuration or a custom list.
|
|
6. **Configurable Logging**: Allows specifying the logging level to use.
|
|
7. **Comprehensive Documentation**: Includes detailed docstring with examples.
|
|
|
|
## Changes to Existing Code
|
|
|
|
The three places where exclude_patterns were checked have been updated to use the new `is_device_excluded` function:
|
|
|
|
1. In the `is_tasmota_device` function:
|
|
```python
|
|
# Check if device should be excluded based on exclude_patterns
|
|
exclude_patterns = network.get('exclude_patterns', [])
|
|
if self.is_device_excluded(name, hostname, exclude_patterns, log_level='debug'):
|
|
return False
|
|
```
|
|
|
|
2. In the `save_tasmota_config` function:
|
|
```python
|
|
# Check if device should be excluded
|
|
if self.is_device_excluded(device_name, device_hostname, exclude_patterns, log_level='info'):
|
|
print(f"Device {device_name} excluded by pattern - skipping")
|
|
excluded_devices.append(device_name)
|
|
continue
|
|
```
|
|
|
|
3. In the `process_single_device` function:
|
|
```python
|
|
# Check if device is excluded
|
|
exclude_patterns = target_network.get('exclude_patterns', [])
|
|
if self.is_device_excluded(device_name, device_hostname, exclude_patterns, log_level='error'):
|
|
return False
|
|
```
|
|
|
|
## Testing
|
|
|
|
A comprehensive test script (`test_is_device_excluded.py`) was created to verify the function's behavior. The tests include:
|
|
|
|
1. Testing with patterns from the configuration
|
|
2. Testing with custom patterns
|
|
3. Testing with different log levels
|
|
|
|
The tests verify that the function correctly identifies devices that should be excluded based on their name or hostname matching any exclude pattern.
|
|
|
|
## Challenges and Solutions
|
|
|
|
During implementation, several challenges were encountered and addressed:
|
|
|
|
1. **Pattern Conversion**: The original implementation escaped dots in the pattern, which caused issues with patterns like `^.*sonos.*`. The solution was to check for patterns that start with `^.*` before doing the glob pattern conversion.
|
|
|
|
2. **Special Pattern Handling**: Patterns like `^.*sonos.*` are meant to match anywhere in the string, but the original implementation didn't handle them correctly. The solution was to extract the part after `^.*` and use `re.search` with this part to match anywhere in the string.
|
|
|
|
3. **Wildcard Handling**: The original implementation required at least one character after the pattern, which caused issues with patterns like `sonos.*` not matching "sonos". The solution was to handle the case where the search part ends with `.*` by removing the `.*` and making it optional.
|
|
|
|
## Recommendations for Future Improvements
|
|
|
|
1. **Refactor Other Pattern Matching**: Consider refactoring other pattern matching code in the script to use a similar approach for consistency.
|
|
|
|
2. **Add Unit Tests**: Add unit tests for the `is_device_excluded` function to ensure it continues to work correctly as the codebase evolves.
|
|
|
|
3. **Optimize Performance**: For large numbers of patterns or devices, consider optimizing the pattern matching logic to improve performance.
|
|
|
|
4. **Enhance Documentation**: Add more examples and explanations to the documentation to help users understand how to use exclude patterns effectively.
|
|
|
|
## Conclusion
|
|
|
|
The implementation of the `is_device_excluded` function centralizes the exclude pattern matching logic, making the code more maintainable and consistent. It properly handles all the special cases and provides a flexible and well-documented interface for checking if a device should be excluded based on its name or hostname matching any exclude pattern. |