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
5.9 KiB
Analysis of exclude_patterns Checks in TasmotaManager.py
Summary
The script performs checks against exclude_patterns in 3 distinct places:
- In the
is_tasmota_devicefunction (lines 165-185) - In the
save_tasmota_configfunction via a localis_device_excludedfunction (lines 423-431) - In the
process_single_devicefunction (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:
# 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:
# 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:
# 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:
-
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
-
Differences:
is_tasmota_deviceandprocess_single_devicehave special handling for patterns that start with^and patterns like^.*something.*save_tasmota_confighas a simpler implementation without these special casesis_tasmota_deviceusesself.logger.debugfor logging, whileprocess_single_deviceusesself.logger.errorsave_tasmota_configdoesn'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:
- Take a device name and hostname as input
- Check if either matches any exclude pattern
- Support case-insensitive matching
- Handle glob patterns (with *)
- Handle patterns that already start with
^ - Have special handling for patterns like
^.*something.* - Include appropriate logging
- 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).