TasmotaManager/docs/summaries/exclude_patterns_analysis.md
2025-10-28 00:21:08 +00:00

5.9 KiB

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:

# 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:

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