Coverage for packages/dqm-ml-core/src/dqm_ml_core/utils/matching.py: 100%

20 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-07-21 08:27 +0000

1"""Wildcard pattern matching utilities for config field resolution. 

2 

3Provides fnmatch-based expansion of ``include`` / ``exclude`` patterns 

4against a list of available strings (column names, split values, etc.). 

5""" 

6 

7import fnmatch 

8 

9 

10def has_pattern(s: str) -> bool: 

11 """Return ``True`` if the string contains any fnmatch wildcard character.""" 

12 return any(ch in s for ch in ("*", "?", "[")) 

13 

14 

15def resolve_patterns(patterns: list[str], available: list[str]) -> list[str]: 

16 """Expand *patterns* against *available* strings using fnmatch. 

17 

18 Literal (non-wildcard) patterns are emitted as-is. Wildcard patterns 

19 are matched against *available* via ``fnmatch.filter``. Order from 

20 *patterns* is preserved (first occurrence wins) and duplicates are 

21 removed. 

22 

23 Parameters 

24 ---------- 

25 patterns: 

26 List of patterns that may contain ``*``, ``?`` or ``[...]``. 

27 available: 

28 The set of concrete strings to match against. 

29 

30 Returns 

31 ------- 

32 A deduplicated, ordered list of matching concrete strings. 

33 """ 

34 result: list[str] = [] 

35 for p in patterns: 

36 if has_pattern(p): 

37 result.extend(fnmatch.filter(available, p)) 

38 else: 

39 result.append(p) 

40 return list(dict.fromkeys(result)) 

41 

42 

43def resolve_include_exclude( 

44 include: list[str] | None, 

45 exclude: list[str] | None, 

46 available: list[str], 

47) -> list[str]: 

48 """Resolve (include, exclude) pattern lists against *available* strings. 

49 

50 1. If *include* is ``None``, every item in *available* is a candidate. 

51 Otherwise, *include* patterns are expanded via :func:`resolve_patterns`. 

52 2. Any candidate matching an *exclude* pattern is removed (exact match 

53 for literal strings, ``fnmatch.filter`` for wildcard patterns). 

54 

55 Parameters 

56 ---------- 

57 include: 

58 Include patterns (``None`` = all). 

59 exclude: 

60 Exclude patterns (``None`` = nothing excluded). 

61 available: 

62 The set of concrete strings to match against. 

63 

64 Returns 

65 ------- 

66 The filtered list (order preserved). 

67 """ 

68 candidates = resolve_patterns(include, available) if include else list(available) 

69 

70 if not exclude: 

71 return candidates 

72 

73 removed: set[str] = set() 

74 for p in exclude: 

75 if has_pattern(p): 

76 removed.update(fnmatch.filter(candidates, p)) 

77 else: 

78 removed.add(p) 

79 

80 return [c for c in candidates if c not in removed]