1. Restructured configuration: Moved config_other and console to top level 2. Added common _match_pattern function for regex pattern matching 3. Implemented Unifi Hostname bug fix in is_hostname_unknown 4. Created common get_device_hostname function to eliminate code duplication 5. Added comprehensive test scripts for all new functionality 6. Added detailed documentation for all changes
126 lines
5.9 KiB
Markdown
126 lines
5.9 KiB
Markdown
# Analysis of exclude_patterns Checks in TasmotaManager.py
|
|
|
|
## Summary
|
|
|
|
The script performs checks against `exclude_patterns` in 3 distinct places:
|
|
|
|
1. In the `is_tasmota_device` function (lines 165-185)
|
|
2. In the `save_tasmota_config` function via a local `is_device_excluded` function (lines 423-431)
|
|
3. In the `process_single_device` function (lines 1589-1610)
|
|
|
|
## Detailed Analysis
|
|
|
|
### 1. In `is_tasmota_device` function (lines 165-185)
|
|
|
|
**Purpose**: During device discovery, this function checks if devices should be excluded based on their name or hostname.
|
|
|
|
**Context**: This is part of the initial device discovery process when scanning the network. The function returns `False` (excluding the device) if the device's name or hostname matches any exclude pattern.
|
|
|
|
**Code snippet**:
|
|
```python
|
|
# Check exclusion patterns
|
|
exclude_patterns = network.get('exclude_patterns', [])
|
|
for pattern in exclude_patterns:
|
|
pattern = pattern.lower()
|
|
# Convert glob pattern to regex pattern
|
|
pattern = pattern.replace('.', r'\.').replace('*', '.*')
|
|
# Check if pattern already starts with ^
|
|
if pattern.startswith('^'):
|
|
regex_pattern = f"{pattern}$"
|
|
# Special case for patterns like ^.*something.* which should match anywhere in the string
|
|
if pattern.startswith('^.*'):
|
|
if re.search(regex_pattern, name) or re.search(regex_pattern, hostname):
|
|
self.logger.debug(f"Excluding device due to pattern '{pattern}': {name} ({hostname})")
|
|
return False
|
|
continue
|
|
else:
|
|
regex_pattern = f"^{pattern}$"
|
|
|
|
# For normal patterns, use re.match which anchors at the beginning of the string
|
|
if re.match(regex_pattern, name) or re.match(regex_pattern, hostname):
|
|
self.logger.debug(f"Excluding device due to pattern '{pattern}': {name} ({hostname})")
|
|
return False
|
|
```
|
|
|
|
### 2. In `save_tasmota_config` function (lines 423-431)
|
|
|
|
**Purpose**: When saving device information to a JSON file, this function checks if devices should be excluded from the current or deprecated lists.
|
|
|
|
**Context**: This function is used during the device tracking process to determine which devices should be included in the output files. It defines a local helper function `is_device_excluded` that checks if a device name or hostname matches any exclude pattern.
|
|
|
|
**Code snippet**:
|
|
```python
|
|
# Function to check if device is excluded
|
|
def is_device_excluded(device_name: str, hostname: str = '') -> bool:
|
|
name = device_name.lower()
|
|
hostname = hostname.lower()
|
|
for pattern in exclude_patterns:
|
|
pattern = pattern.lower()
|
|
pattern = pattern.replace('.', r'\.').replace('*', '.*')
|
|
if re.match(f"^{pattern}$", name) or re.match(f"^{pattern}$", hostname):
|
|
return True
|
|
return False
|
|
```
|
|
|
|
### 3. In `process_single_device` function (lines 1589-1610)
|
|
|
|
**Purpose**: When processing a single device by IP or hostname, this function checks if the device should be excluded based on its name or hostname.
|
|
|
|
**Context**: This function is used in Device mode (triggered by the `--Device` command-line argument) to determine if a specific device should be processed. It returns `False` (skipping the device) if the device's name or hostname matches any exclude pattern.
|
|
|
|
**Code snippet**:
|
|
```python
|
|
# Check if device is excluded
|
|
exclude_patterns = target_network.get('exclude_patterns', [])
|
|
for pattern in exclude_patterns:
|
|
pattern_lower = pattern.lower()
|
|
pattern_regex = pattern_lower.replace('.', r'\.').replace('*', '.*')
|
|
# Check if pattern already starts with ^
|
|
if pattern_regex.startswith('^'):
|
|
regex_pattern = f"{pattern_regex}$"
|
|
# Special case for patterns like ^.*something.* which should match anywhere in the string
|
|
if pattern_regex.startswith('^.*'):
|
|
if (re.search(regex_pattern, device_name.lower()) or
|
|
re.search(regex_pattern, device_hostname.lower())):
|
|
self.logger.error(f"Device {device_name} is excluded by pattern: {pattern}")
|
|
return False
|
|
continue
|
|
else:
|
|
regex_pattern = f"^{pattern_regex}$"
|
|
|
|
# For normal patterns, use re.match which anchors at the beginning of the string
|
|
if (re.match(regex_pattern, device_name.lower()) or
|
|
re.match(regex_pattern, device_hostname.lower())):
|
|
self.logger.error(f"Device {device_name} is excluded by pattern: {pattern}")
|
|
return False
|
|
```
|
|
|
|
## Pattern Matching Logic Comparison
|
|
|
|
The pattern matching logic is similar across these locations, but there are some differences:
|
|
|
|
1. **Common elements**:
|
|
- All implementations convert patterns to lowercase for case-insensitive matching
|
|
- All implementations convert glob patterns (with *) to regex patterns
|
|
- All implementations check if the device name or hostname matches any exclude pattern
|
|
|
|
2. **Differences**:
|
|
- `is_tasmota_device` and `process_single_device` have special handling for patterns that start with `^` and patterns like `^.*something.*`
|
|
- `save_tasmota_config` has a simpler implementation without these special cases
|
|
- `is_tasmota_device` uses `self.logger.debug` for logging, while `process_single_device` uses `self.logger.error`
|
|
- `save_tasmota_config` doesn't include any logging
|
|
|
|
## Recommendation
|
|
|
|
Based on this analysis, a common function for exclude_patterns checks would be beneficial to ensure consistent pattern matching behavior across the codebase. This function should:
|
|
|
|
1. Take a device name and hostname as input
|
|
2. Check if either matches any exclude pattern
|
|
3. Support case-insensitive matching
|
|
4. Handle glob patterns (with *)
|
|
5. Handle patterns that already start with `^`
|
|
6. Have special handling for patterns like `^.*something.*`
|
|
7. Include appropriate logging
|
|
8. Return a boolean indicating if the device should be excluded
|
|
|
|
This would be similar to the `is_hostname_unknown` function that was implemented for unknown_device_patterns, but with the opposite return value logic (return `True` if the device should be excluded, `False` otherwise). |