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). |